EmDash 在 /_emdash/api/mcp 路径内置了 Model Context Protocol(MCP)服务器,将内容管理操作作为工具暴露给 AI 助手。
本页涵盖协议详情:认证、传输、工具规范、OAuth 发现和错误处理。
认证
MCP 服务器支持三种认证方式:
| 方式 | 工作原理 |
|---|---|
| OAuth 2.1 Authorization Code + PKCE | MCP 客户端的标准流程。用户在浏览器中批准权限范围。 |
| 个人访问令牌(PAT) | 在管理面板中创建的长期有效 ec_pat_* 令牌。 |
| 设备流 | CLI 风格的流程,在浏览器中批准代码。由 emdash login 使用。 |
会话 Cookie(来自管理 UI)也可用,但对外部 MCP 客户端不实用。
权限范围
令牌通过范围限制客户端可执行的操作。范围在 OAuth 授权期间请求,并在每次工具调用时强制执行。
| 范围 | 授予的访问权限 |
|---|---|
content:read | 列出、获取、比较和搜索内容。列出分类法术语和菜单。 |
content:write | 创建、更新、删除、发布、取消发布、定时发布、复制和恢复内容。创建分类法术语。 |
media:read | 列出和获取媒体项。 |
media:write | 更新和删除媒体元数据。 |
schema:read | 列出集合和获取集合模式。 |
schema:write | 创建和删除集合及字段。 |
admin | 所有操作的完全访问权限。 |
admin 范围授予对所有内容的访问权限。基于会话的认证(无令牌)也根据用户角色拥有完全访问权限。
角色要求
除权限范围外,某些工具需要最低 RBAC 角色:
| 操作 | 最低角色 |
|---|---|
| 内容操作 | 无最低要求(由范围控制访问) |
| 模式读取 | 编辑者(40) |
| 模式写入 | 管理员(50) |
角色定义参见认证指南。
传输
服务器使用无状态模式的 Streamable HTTP 传输。每个请求是独立的——没有会话或长期连接。
POST /_emdash/api/mcp— 发送 JSON-RPC 工具调用GET /_emdash/api/mcp— 返回 405(无状态模式下无 SSE)DELETE /_emdash/api/mcp— 返回 405(无会话可关闭)
响应遵循 JSON-RPC 2.0 格式。错误使用标准 JSON-RPC 错误码,范围和权限失败使用 MCP 特定码。
工具
服务器在七个领域暴露 33 个工具。每个工具以 JSON 文本内容返回结果,失败时返回带有 isError: true 的错误消息。
内容工具
content_list
列出集合中的内容项,支持可选过滤和分页。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug(如 posts、pages) |
status | string | 否 | 过滤:draft、published 或 scheduled |
limit | integer | 否 | 最大返回数量(1-100,默认 50) |
cursor | string | 否 | 上一次响应的分页游标 |
orderBy | string | 否 | 排序字段(如 created_at、updated_at) |
order | string | 否 | 排序方向:asc 或 desc(默认 desc) |
locale | string | 否 | 按语言环境过滤(如 en、fr)。仅在启用 i18n 时相关。 |
范围: content:read | 只读: 是
content_get
按 ID 或 slug 获取单个内容项。返回所有字段值、元数据和用于乐观并发的 _rev 令牌。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID(ULID)或 slug |
locale | string | 否 | 用于 slug 查找的语言环境。ID 全局唯一。 |
范围: content:read | 只读: 是
content_create
创建新的内容项。data 对象应包含与集合模式匹配的字段值——使用 schema_get_collection 检查可用字段。默认创建为 draft。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
data | object | 是 | 键值对形式的字段值 |
slug | string | 否 | URL slug(省略时从标题自动生成) |
status | string | 否 | 初始状态:draft 或 published(默认 draft) |
locale | string | 否 | 此内容的语言环境(默认为站点默认值) |
translationOf | string | 否 | 此条目是其翻译的原条目 ID |
范围: content:write
content_update
更新现有内容项。只需包含要更改的字段——未指定的字段保持不变。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
data | object | 否 | 要更新的字段值 |
slug | string | 否 | 新 URL slug |
status | string | 否 | 新状态:draft 或 published |
_rev | string | 否 | 来自 content_get 的修订令牌,用于冲突检测 |
范围: content:write
content_delete
将内容项软删除至回收站。使用 content_restore 恢复,或使用 content_permanent_delete 永久删除。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
范围: content:write | 破坏性: 是
content_restore
从回收站恢复软删除的内容项。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
范围: content:write
content_permanent_delete
永久且不可逆地删除已回收的内容项。该项必须先在回收站中。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
范围: content:write | 破坏性: 是
content_publish
发布内容项,使其在站点上线。从当前草稿创建已发布版本。后续编辑会创建新草稿,不影响线上版本直到重新发布。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
范围: content:write
content_unpublish
将已发布项恢复为草稿状态。它将不再在线上站点可见,但内容会被保留。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
范围: content:write
content_schedule
定时发布内容项。它将在指定的日期/时间自动发布。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
scheduledAt | string | 是 | ISO 8601 日期时间(如 2026-06-01T09:00:00Z) |
范围: content:write
content_compare
比较内容项的已发布(线上)版本与当前草稿。返回两个版本和一个是否有更改的标志。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
范围: content:read | 只读: 是
content_discard_draft
丢弃当前草稿并恢复到最后发布的版本。仅对至少发布过一次的项有效。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
范围: content:write | 破坏性: 是
content_list_trashed
列出集合回收站中的软删除内容项。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
limit | integer | 否 | 最大数量(1-100,默认 50) |
cursor | string | 否 | 分页游标 |
范围: content:read | 只读: 是
content_duplicate
复制现有内容项。副本创建为草稿,标题追加”(Copy)“,slug 自动生成。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 要复制的内容项 ID 或 slug |
范围: content:write
content_translations
获取内容项的所有语言变体。返回翻译组和每个语言版本的摘要。仅在启用 i18n 时相关。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
范围: content:read | 只读: 是
模式工具
schema_list_collections
列出 CMS 中定义的所有内容集合。返回 slug、标签、支持的功能和时间戳。
无参数。
范围: schema:read | 最低角色: 编辑者 | 只读: 是
schema_get_collection
获取集合的详细信息,包括所有字段定义。字段描述数据模型:名称、类型、约束和验证规则。使用此工具了解 content_create 和 content_update 期望的内容。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
slug | string | 是 | 集合 slug(如 posts) |
范围: schema:read | 最低角色: 编辑者 | 只读: 是
schema_create_collection
创建新的内容集合。这会创建数据库表和模式定义。slug 必须是以字母开头的小写字母数字加下划线。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
slug | string | 是 | 唯一标识符(/^[a-z][a-z0-9_]*$/) |
label | string | 是 | 显示名称(复数,如 “Blog Posts”) |
labelSingular | string | 否 | 单数显示名称 |
description | string | 否 | 此集合的描述 |
icon | string | 否 | 管理 UI 的图标名称 |
supports | string[] | 否 | 功能:drafts、revisions、preview、scheduling、search(默认:['drafts', 'revisions']) |
范围: schema:write | 最低角色: 管理员
schema_delete_collection
删除集合及其数据库表。此操作不可逆,会删除集合中的所有内容。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
slug | string | 是 | 要删除的集合 slug |
force | boolean | 否 | 即使集合有内容也强制删除 |
范围: schema:write | 最低角色: 管理员 | 破坏性: 是
schema_create_field
向集合模式添加新字段。这会向数据库表添加列。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
slug | string | 是 | 字段标识符(/^[a-z][a-z0-9_]*$/) |
label | string | 是 | 显示名称 |
type | string | 是 | 数据类型(见下文) |
required | boolean | 否 | 字段是否必填 |
unique | boolean | 否 | 值是否必须唯一 |
defaultValue | any | 否 | 新条目的默认值 |
validation | object | 否 | 约束:min、max、minLength、maxLength、pattern、options |
options | object | 否 | 小部件配置:collection(用于引用)、rows(用于文本区域) |
searchable | boolean | 否 | 是否纳入全文搜索索引 |
translatable | boolean | 否 | 此字段是否可翻译(默认 true) |
字段类型:string、text、number、integer、boolean、datetime、select、multiSelect、portableText、image、file、reference、json、slug。
对于 select 和 multiSelect 类型,在 validation.options 中提供允许的值。
范围: schema:write | 最低角色: 管理员
schema_delete_field
从集合中移除字段。这会删除列并删除该字段的所有数据。不可逆。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
fieldSlug | string | 是 | 要移除的字段 slug |
范围: schema:write | 最低角色: 管理员 | 破坏性: 是
媒体工具
media_list
列出已上传的媒体文件,支持可选 MIME 类型过滤和分页。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
mimeType | string | 否 | 按 MIME 类型前缀过滤(如 image/、application/pdf) |
limit | integer | 否 | 最大数量(1-100,默认 50) |
cursor | string | 否 | 分页游标 |
范围: media:read | 只读: 是
media_get
按 ID 获取单个媒体文件的详情。返回包括文件名、MIME 类型、大小、尺寸、替代文本和 URL 的元数据。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
id | string | 是 | 媒体项 ID |
范围: media:read | 只读: 是
media_update
更新已上传媒体文件的元数据。文件本身不能更改。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
id | string | 是 | 媒体项 ID |
alt | string | 否 | 无障碍替代文本 |
caption | string | 否 | 说明文字 |
width | integer | 否 | 图片宽度(像素) |
height | integer | 否 | 图片高度(像素) |
范围: media:write
media_delete
永久删除媒体文件。移除数据库记录和存储中的文件。引用此媒体的内容将出现断裂引用。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
id | string | 是 | 媒体项 ID |
范围: media:write | 破坏性: 是
搜索工具
search
跨内容集合的全文搜索。集合必须在 supports 列表中包含 search,字段必须标记为 searchable。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
query | string | 是 | 搜索查询文本 |
collections | string[] | 否 | 限制搜索特定集合 slug |
locale | string | 否 | 按语言环境过滤结果 |
limit | integer | 否 | 最大结果数(1-50,默认 20) |
范围: content:read | 只读: 是
分类法工具
taxonomy_list
列出所有分类法定义(如分类目录、标签)。返回名称、标签、是否层级以及关联的集合。
无参数。
范围: content:read | 只读: 是
taxonomy_list_terms
列出分类法中的术语,支持分页。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
taxonomy | string | 是 | 分类法名称(如 categories、tags) |
limit | integer | 否 | 最大数量(1-100,默认 50) |
cursor | string | 否 | 分页游标 |
范围: content:read | 只读: 是
taxonomy_create_term
在分类法中创建新术语。对于层级分类法,指定 parentId 创建子术语。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
taxonomy | string | 是 | 分类法名称 |
slug | string | 是 | URL 安全标识符 |
label | string | 是 | 显示名称 |
parentId | string | 否 | 父术语 ID(用于层级分类法) |
description | string | 否 | 术语描述 |
范围: content:write
菜单工具
menu_list
列出所有导航菜单。返回名称、标签和时间戳。
无参数。
范围: content:read | 只读: 是
menu_get
按名称获取菜单,包括所有有序项目。项目包含标签、URL、类型和可选的父级(用于嵌套)。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
name | string | 是 | 菜单名称(如 main、footer) |
范围: content:read | 只读: 是
修订工具
revision_list
列出内容项的修订历史,最新在前。需要集合支持 revisions。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
collection | string | 是 | 集合 slug |
id | string | 是 | 内容项 ID 或 slug |
limit | integer | 否 | 最大修订数(1-50,默认 20) |
范围: content:read | 只读: 是
revision_restore
将内容项恢复到之前的修订。用指定修订的数据替换当前草稿。不会自动发布——如需要请之后使用 content_publish。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
revisionId | string | 是 | 要恢复的修订 ID |
范围: content:write
OAuth 发现
支持 OAuth 2.1 的 MCP 客户端可以自动发现认证方式。服务器发布两个元数据文档:
受保护资源元数据
GET /.well-known/oauth-protected-resource
{
"resource": "https://example.com/_emdash/api/mcp",
"authorization_servers": ["https://example.com/_emdash"],
"scopes_supported": [
"content:read", "content:write",
"media:read", "media:write",
"schema:read", "schema:write",
"admin"
],
"bearer_methods_supported": ["header"]
}
授权服务器元数据
GET /_emdash/.well-known/oauth-authorization-server
{
"issuer": "https://example.com/_emdash",
"authorization_endpoint": "https://example.com/_emdash/oauth/authorize",
"token_endpoint": "https://example.com/_emdash/api/oauth/token",
"scopes_supported": ["content:read", "content:write", "..."],
"response_types_supported": ["code"],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code"
],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"device_authorization_endpoint": "https://example.com/_emdash/api/oauth/device/code"
}
当未认证的请求到达 MCP 端点时,服务器返回:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"
这会触发标准的 MCP 客户端发现流程。
错误处理
工具错误以带有 isError: true 的文本内容返回:
{
"content": [{ "type": "text", "text": "Collection 'nonexistent' not found" }],
"isError": true
}
范围和权限错误抛出 MCP 协议错误:
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Insufficient scope: requires content:write"
},
"id": 1
}
传输层错误(服务器配置错误、未处理异常)返回 JSON-RPC 错误码 -32603(内部错误),不泄露实现细节。