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 クライアント。ユーザーがブラウザーで、要求されたスコープを承認します。 |
| Personal access token | クライアントや自動化向けの長期間有効なアクセス。トークンには ec_pat_ プレフィックスが付き、管理画面で作成します。 |
| OAuth 2.0 Device Authorization Grant | ブラウザーでコードを承認するようユーザーに求めるコマンドラインクライアント。emdash login はこのフローを使用します。 |
セッション Cookie では MCP エンドポイントを認証できません。
スコープ
OAuth トークンとパーソナルアクセストークンは、クライアントが呼び出せるツールを制限します。ユーザーのロールは別途チェックされるため、スコープがユーザーの持たない権限を付与することはありません。
| スコープ | アクセス |
|---|---|
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> | 有効な特定の 1 つのプラグインが公開する MCP ツールの呼び出し。 |
transfer:export | サイト全体をサイトパッケージとしてエクスポートし、ダウンロードする。 |
transfer:analyze | サイトパッケージをアップロードし、インポート用に分析する。 |
transfer:execute | サイトインポートの開始、進行、キャンセル、破棄。 |
admin | サイト転送ツールを含む、すべてのコアツールの呼び出し。プラグインツールには、引き続き mcp:tools またはプラグイン固有のスコープが必要です。 |
admin スコープには、transfer:export、transfer:analyze、transfer:execute が含まれます。各転送スコープは、それぞれ自身の操作だけを許可し、管理者ロールを必要とします。エージェントなどのクライアントに、エクスポートやインポートは許可せずにサイトパッケージの分析だけを行わせるには、admin ではなく transfer:analyze を付与してください。
認可コードの同意ページでは、ユーザーが要求されたスコープを取り除けます。EmDash はさらに、リクエストをクライアントの登録済みスコープおよびユーザーのロールと突き合わせて絞り込み、空の付与は拒否します。
ロール要件
次の表は、大まかな機能ごとの最小ロールを示しています。ユーザーが他のユーザーのコンテンツを操作する場合は、所有権のチェックにより、より高いロールが必要になることがあります。
| 機能 | 最小ロール |
|---|---|
| 公開済みのコンテンツ、メディア、タクソノミー、ターム、メニューの読み取り | 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 を呼び出して、現在の入力スキーマと MCP アノテーションを取得してください。
ツール一覧
次の一覧は、tools/list が返す静的ツールと一致しています。クライアントがツール名の代わりにタイトルを表示することがあるため、登録済みのタイトルも掲載しています。
コンテンツツール
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
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_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 |
スキーマツール
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
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 |
メディアツール
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
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 |
検索ツール
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
search | Search Content | content:read |
タクソノミーツール
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
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 |
メニューツール
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
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 |
リビジョンツール
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
revision_list | List Revisions | content:read |
revision_restore | Restore Revision | content:write |
設定ツール
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
settings_get | Get Site Settings | settings:read |
settings_update | Update Site Settings | settings:manage |
サイト転送ツール
transfer:* は、transfer:export、transfer:analyze、transfer:execute のいずれか 1 つを意味します。admin スコープは、この表のすべての要件を満たします。
| ツール | 登録済みタイトル | 必要なスコープ |
|---|---|---|
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 は、管理者がリクエストを承認した場合、そのスコープを持たないトークンも受け付けます。承認されたリクエストが開始した操作については、site_export_status、site_import_status、site_import_resume、site_import_receipt も、そのスコープを持たない同じトークンを受け付けます。
ツールスキーマの利用
tools/list は、各ツールの説明、JSON 入力スキーマ、アノテーションを返します。呼び出しを組み立てる前にこのメタデータを読み、クライアントがインストール済みの 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 テキストとして返されます。出力スキーマを持つツールは、同じ値を structuredContent にも返すことがあります。
コンテンツライフサイクルとバイライン
コンテンツライフサイクルのリファレンスでは、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_create は、ゲストのクレジットを作成することも、バイラインを CMS ユーザーにリンクすることもできます。1 人のユーザーが持てるバイラインは、ロケールごとに 1 つだけです。translationOf で作成した翻訳は、userId を渡さない限り、元のバイラインのユーザーを引き継ぎます。null を渡すと、その翻訳はユーザーにリンクされません。返されたバイライン ID を、content_create と content_update が受け付ける bylines 入力に渡してください。バイラインを削除すると、そのクレジットがコンテンツから取り除かれ、メインのバイラインとしての設定もクリアされます。
別のユーザーが管理画面でエントリを開いていて、その編集ロックを保持している間、content_update、content_delete、content_publish、content_unpublish、content_schedule、content_unschedule、content_discard_draft、revision_restore は ENTRY_LOCKED で失敗し、エラーには保持者が示されます。アイテムを読み直しても、この拒否は解除されません。強制的に書き込むには、overrideLock: true を渡します。
翻訳
コンテンツ、バイライン、タクソノミーターム、メニューの翻訳ツールは、該当する翻訳グループ内のすべてのロケールのバリアントを返します。作成ツールのスキーマに translationOf 入力がある場合は、それを使用してください。必須フィールドについては tools/list が正となります。
content_translations は、コレクションと、コンテンツ ID またはスラグを受け付けます。バイライン、タクソノミーターム、メニューの翻訳ツールは、1 件のレコードの ID か、共有の翻訳グループ ID のどちらかを受け付けます。下書きへのアクセス権を持たないユーザーには、公開済みのコンテンツの翻訳だけが表示されます。
スキーマ、メディア、タクソノミー、メニュー
スキーマツールは、データベースの構造を変更します。コンテンツを作成したりフィールドを変更したりする前に、schema_get_collection を使用してください。このツールは、利用可能なフィールド名、型、制約、バリデーションルールを返します。コレクションとフィールドの削除は、保存されているコンテンツやフィールドの値を削除し、元に戻せません。
base64 エンコードされたバイト列を送信するには、media_upload を使用します。アップロードは、設定されたサイズと MIME タイプの制限の対象となり、同一のバイト列は、deduplicated: true とともに既存のメディアアイテムを返すことがあります。
media_create は、POST /_emdash/api/media/upload-url で作成された保留中のアップロードを確定します。返された署名付き URL でファイルをアップロードし、同じユーザーアカウントから、返された storageKey を指定して media_create を呼び出してください。このツールは、保存されたファイルが存在し、アップロード URL のリクエスト時に指定されたサイズと一致することを確認してから、メディアライブラリで使えるようにします。
タクソノミー定義は分類方法と、それが適用されるコレクションを記述します。タームは、コンテンツに割り当てられる個々の値です。階層型のタームでは parentId を使えますが、親は同じタクソノミーに属している必要があり、循環を作ることはできません。階層型ではないタクソノミーで parentId を指定してタームを作成または更新すると、VALIDATION_ERROR が返されます。子を持つタームは、子を削除または移動してからでないと削除できません。
menu_set_items は、1 回のアトミックな操作で、メニューの項目リスト全体を置き換えます。配列の順序がメニューの順序になります。ネストされた項目の parentIndex は、同じ配列内でそれより前にある項目を指すため、すべての親を子より前に配置してください。
media_usage_repair は、1 つのコレクションまたはすべてのコレクションを処理でき、大規模なサイトでは長時間実行されることがあります。complete、partial、failed、stale の各ステータスは、成功したツールレスポンスです。isError に頼らず、返されたステータスと件数を確認してください。認証、バリデーション、予期しない実行の失敗では、isError: true が設定されます。
サイト転送
site_* ツールは、サイト全体をサイトパッケージとしてエクスポートし、パッケージを空のサイトにインポートします。これらのツールは操作を開始して進行させ、上限のある要約を返します。パッケージのバイト列、メディア、レコードの内容、プリンシパルのメールアドレス、ダウンロード URL は、決して扱いません。エクスポートのダウンロードと、インポート用のパッケージのアップロードは、CLI または REST API で行い、その後は ID で操作を参照します。
すべての転送ツールには Admin ロールが必要です。ロールはスコープより先にチェックされるため、管理者ではない呼び出し元は INSUFFICIENT_PERMISSIONS を受け取り、承認リクエストは作成されません。
エクスポート
site_export_start は comments(デフォルトは true)を受け付け、新しい操作を返します。site_export_status は、呼び出しごとに上限のあるエクスポートのステップを 1 つ実行し、操作と nextRequestInMs を報告します。nextRequestInMs が null になるまで、その遅延の後に再度呼び出してください。ステップを実行せずにステータスだけを読み取るには、advance: false を渡します。エクスポートが完了すると、結果には totals も含まれます。これは、種類ごとのレコード数、メディアの数とバイト数、パッケージのファイル数とバイト数です。
インポート
先にパッケージをアップロードしてください。CLI の emdash site import <file> --analyze は、パッケージをアップロードして分析し、操作 ID を出力します。
site_import_analyze は、呼び出しごとに上限のある分析ステップを 1 つ実行します。nextRequestInMs が null になるまで繰り返してください。その後、結果には、packageDigest、planDigest、executable、件数、サイズ、プリンシパル、決定事項、変換、警告、ブロッカーを含む、プランの要約が含まれます。各変換は、その code、レコードの kind(ある場合)、count として列挙され、適用対象の ID や値は含まれません。プリンシパル、警告、ブロッカーは、それぞれ最大 50 件まで列挙され、全件数は total に入ります。プリンシパルはメールアドレスなしで列挙され、推奨されるターゲットユーザー ID と、現在マッピングされているターゲットユーザー ID が示されます。decisions を渡すと、プリンシパルをターゲットユーザー ID(または null)にマッピングし、パッケージ側かターゲット側のタイトルとタグラインを選択できます。変更のたびに、新しい planDigest が生成されます。
site_import_start は、操作 ID と、最新のプランの packageDigest および planDigest を受け取ります。プランにブロッカーがあってはいけません。このツールには destructiveHint: true が設定されています。開始すると、インポートはサイトに書き込みを行い、完了するか管理者が破棄するまで、他の書き込みをブロックします。呼び出す前に、ユーザーにプランを提示して確認を得てください。
site_import_resume は、上限のあるインポートのステップを 1 つ実行し、操作と nextRequestInMs を報告します。nextRequestInMs が null になるまで呼び出してください。切断後に繰り返しても安全です。site_import_status は、インポートを進めずに、操作とアップロード済みファイルの数を報告します。site_import_receipt は、インポートが完了した後に、receiptDigest を含む完全なレシートを返します。
インポートの実行中、および失敗またはキャンセルの後、破棄されるまでの間は、書き込みが可能な他のすべてのツールが TRANSFER_IMPORT_IN_PROGRESS で失敗します。これにはプラグインツールも含まれます。readOnlyHint: true が付いたツールと 8 つの 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 または必要な転送スコープを持つトークンは、承認を求められません。どちらも持たないトークン(たとえば transfer:analyze だけを付与されたエージェント)の場合、site_export_start と site_import_start は、管理者がリクエストを承認すると実行されます。
- スコープなしでの最初の呼び出しは、保留中の承認リクエストを作成し、
TRANSFER_APPROVAL_REQUIREDで失敗します。メッセージのテキストと_meta.detailsに、approvalIdとそのexpiresAtが含まれます。同じ引数でapprovalIdなしに再度呼び出すと、同じ未処理のリクエストが返されます。 - 管理者は、Settings → Transfer の Approval requests でリクエストを承認するか、REST API のセッション専用の承認エンドポイントで承認します。API トークンではリクエストを承認できません。
- クライアントは、同じ引数と
approvalIdを指定して、呼び出しを繰り返します。その呼び出しが操作を開始した時点で、承認は使い切られます。操作の開始に失敗した場合、クライアントは、期限が切れるまで同じapprovalIdで再試行できます。
リクエストは、ユーザー、トークン、アクション、そして正確な引数(エクスポートのオプション、またはインポートの操作 ID と 2 つのダイジェスト)に紐づけられます。保留中のリクエストは作成から 15 分で、承認済みのリクエストは承認から 15 分で期限切れになります。引数が異なる呼び出し、別のトークンでの呼び出し、または拒否済み、期限切れ、使用済みの承認を指定した呼び出しは、TRANSFER_APPROVAL_INVALID で失敗します。
site_import_start は、リクエストを作成する前に、ダイジェスト、操作の状態、プランのブロッカーをチェックします。そのため、管理者に承認を求めるのは、実行可能なインポートだけです。承認にはトークン ID が必要なので、トークン ID を持たない呼び出し元は INSUFFICIENT_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
このドキュメントは、現在の認可、トークン、登録、デバイス認可の各エンドポイント、サポートされているスコープ、グラントタイプ、S256 PKCE メソッドを提供します。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" }
}
エントリの編集ロックによる拒否では、_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 が使われ、内部の例外は公開されません。