EmDash 在 /_emdash/api/mcp 提供內建的 Model Context Protocol(MCP)伺服器。MCP 用戶端透過它讀取和管理內容、Byline、結構描述、媒體、分類法、選單、修訂和設定,並匯出或匯入整個站台。
驗證
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 | 讀取和搜尋內容、Byline、分類法、術語、選單和修訂。類草稿內容還需要使用者具有 content:read_drafts 權限。 |
content:write | 建立和變更內容、Byline 和修訂。為了與現有權杖相容,它還會授予 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 |
| 管理 Byline、分類法、選單或所有使用者的內容 | 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 傳回的靜態工具一致。其中包含已註冊的標題,因為用戶端可能會顯示標題而不是工具名稱。
內容工具
| 工具 | 已註冊標題 | 所需 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 |
Byline 工具
| 工具 | 已註冊標題 | 所需 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 |
結構描述工具
| 工具 | 已註冊標題 | 所需 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 |
媒體工具
| 工具 | 已註冊標題 | 所需 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 |
搜尋工具
| 工具 | 已註冊標題 | 所需 scope |
|---|---|---|
search | Search Content | content:read |
分類法工具
| 工具 | 已註冊標題 | 所需 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 |
選單工具
| 工具 | 已註冊標題 | 所需 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 |
修訂工具
| 工具 | 已註冊標題 | 所需 scope |
|---|---|---|
revision_list | List Revisions | content:read |
revision_restore | Restore Revision | content:write |
設定工具
| 工具 | 已註冊標題 | 所需 scope |
|---|---|---|
settings_get | Get Site Settings | settings:read |
settings_update | Update Site Settings | settings:manage |
站台轉移工具
transfer:* 表示 transfer:export、transfer:analyze 或 transfer:execute 中的任意一個。admin scope 滿足此表中的每一項要求。
| 工具 | 已註冊標題 | 所需 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 中傳回相同的值。
內容生命週期與 Byline
內容生命週期參考定義了 MCP、REST、CLI 和管理面板共用的狀態、修訂、權限、衝突和勾點行為。
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 是可重複使用的作者或貢獻者署名。byline_create 可以建立訪客署名,也可以把 Byline 關聯到 CMS 使用者。每個使用者在每個區域設定下只能有一個 Byline。使用 translationOf 建立的翻譯會保留來源 Byline 的使用者,除非你傳入 userId;傳入 null 則使該翻譯不關聯任何使用者。把傳回的 Byline ID 傳入 content_create 和 content_update 接受的 bylines 輸入。刪除 Byline 會從內容中移除該署名,並清除其作為主要 Byline 的設定。
當另一位使用者因在管理後台中開啟某個條目而持有它的編輯鎖時,content_update、content_delete、content_publish、content_unpublish、content_schedule、content_unschedule、content_discard_draft 和 revision_restore 會以 ENTRY_LOCKED 失敗,錯誤訊息會指明持有者。重新讀取該條目並不會消除這一拒絕。傳入 overrideLock: true 可強制寫入。
翻譯
內容、Byline、分類法術語和選單的翻譯工具會傳回相應翻譯群組中的每一個區域設定變體。當建立工具的 schema 提供 translationOf 輸入時,請使用它;必填欄位以 tools/list 為準。
content_translations 接受集合以及內容 ID 或 slug。Byline、分類法術語和選單的翻譯工具接受單一記錄的 ID 或共用的翻譯群組 ID。沒有草稿存取權限的使用者只能看到已發佈的內容翻譯。
結構描述、媒體、分類法與選單
結構描述工具會變更資料庫結構。在建立內容或變更欄位之前,請先使用 schema_get_collection;它會傳回可用的欄位名稱、類型、限制和校驗規則。刪除集合和欄位會移除已儲存的內容或欄位值,且無法復原。
使用 media_upload 傳送 base64 編碼的位元組。上傳受已設定的大小和 MIME 類型限制,相同的位元組可能會傳回已有的媒體項目,並帶有 deduplicated: true。
media_create 用於確認透過 POST /_emdash/api/media/upload-url 建立的待處理上傳。使用傳回的簽署 URL 上傳檔案,然後用同一個使用者帳號呼叫 media_create,並傳入傳回的 storageKey。該工具會檢查已儲存的檔案是否存在,並且大小與請求上傳 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再次呼叫,會傳回同一個未關閉的請求。 - 管理員在 Settings → Transfer 的 Approval requests 下核准該請求,或使用 REST API 中僅限工作階段的核准端點。API 權杖不能核准請求。
- 用戶端使用相同的引數和
approvalId重複呼叫。該呼叫啟動操作時,這次核准即被用掉。如果操作啟動失敗,用戶端可以使用同一個approvalId重試,直到它過期。
請求與使用者、權杖、操作和確切的引數綁定:匯出選項,或匯入操作 ID 與兩個摘要。待處理的請求在建立 15 分鐘後過期,已核准的請求在核准 15 分鐘後過期。使用不同引數或另一個權杖的呼叫,或使用已拒絕、已過期或已用掉的核准的呼叫,會以 TRANSFER_APPROVAL_INVALID 失敗。
site_import_start 會先檢查摘要、操作狀態和計畫的阻擋項,然後才建立請求,因此只會請求管理員核准可以執行的匯入。核准需要權杖 ID,因此沒有權杖 ID 的呼叫者會收到 INSUFFICIENT_SCOPE。
已核准的呼叫啟動某個操作之後,同一使用者和權杖可以針對該操作呼叫 site_export_status,或 site_import_status、site_import_resume 和 site_import_receipt,而無需該 scope。
外掛程式工具
管理員必須啟用每個外掛程式的 MCP 介面。已啟用的工具會以 <pluginId>__<localName> 的形式出現在 tools/list 中,並且對於使用權杖驗證的呼叫,需要 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、授權類型和 S256 PKCE 方法。請使用探索到的值,而不要硬式編碼 OAuth 協定路由。
未驗證的 MCP 請求會傳回 401 回應,其中包含探索 URL:
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" }
}
來自條目編輯鎖的拒絕會在 _meta.details 中攜帶持有者資訊:
{
"content": [{ "type": "text", "text": "[ENTRY_LOCKED] Ada is holding this entry" }],
"isError": true,
"_meta": {
"code": "ENTRY_LOCKED",
"details": {
"userId": "01JB...",
"userName": "Ada",
"acquiredAt": "2026-05-01T09:12:04.117Z",
"expiresAt": "2026-05-01T09:19:04.117Z"
}
}
}
驗證失敗使用 INSUFFICIENT_SCOPE 和 INSUFFICIENT_PERMISSIONS 等錯誤碼。傳輸失敗使用 JSON-RPC 內部錯誤碼 -32603,並且不會公開底層例外。