EmDash expose un serveur Model Context Protocol (MCP) intégré à /_emdash/api/mcp. Les clients MCP l’utilisent pour lire et gérer le contenu, les bylines, les schémas, les médias, les taxonomies, les menus, les révisions et les paramètres, et pour exporter ou importer l’ensemble du site.
Authentification
Le point de terminaison MCP exige un jeton Bearer. EmDash prend en charge ces flux de jetons :
| Méthode | Usage |
|---|---|
| OAuth 2.1 Authorization Code with Proof Key for Code Exchange (PKCE) | Clients MCP interactifs. L’utilisateur approuve les scopes demandés dans le navigateur. |
| Personal access token | Accès de longue durée pour un client ou une automatisation. Les jetons utilisent le préfixe ec_pat_ et sont créés dans l’admin. |
| OAuth 2.0 Device Authorization Grant | Clients en ligne de commande qui demandent à l’utilisateur d’approuver un code dans le navigateur. emdash login utilise ce flux. |
Les cookies de session n’authentifient pas le point de terminaison MCP.
Scopes
Les jetons OAuth et d’accès personnel limitent les outils qu’un client peut appeler. Le rôle de l’utilisateur est vérifié séparément : un scope n’accorde jamais une permission que l’utilisateur n’a pas.
| Scope | Accès |
|---|---|
content:read | Lire et rechercher contenu, bylines, taxonomies, termes, menus et révisions. Le contenu de type brouillon exige aussi la permission content:read_drafts de l’utilisateur. |
content:write | Créer et modifier contenu, bylines et révisions. Accorde aussi taxonomies:manage et menus:manage pour compatibilité avec les jetons existants. |
media:read | Lire les enregistrements média. |
media:write | Téléverser, enregistrer, mettre à jour et supprimer des médias. |
schema:read | Lire collections et champs. |
schema:write | Créer, mettre à jour et supprimer collections et champs. |
taxonomies:manage | Créer, mettre à jour et supprimer définitions de taxonomie et termes. |
menus:manage | Créer, mettre à jour et supprimer menus et éléments de menu. |
settings:read | Lire les paramètres du site. |
settings:manage | Mettre à jour les paramètres du site. |
mcp:tools | Appeler les outils MCP exposés par tout plugin activé. |
mcp:tools:<pluginId> | Appeler les outils MCP exposés par un plugin activé. |
transfer:export | Exporter l’ensemble du site en paquet de site et le télécharger. |
transfer:analyze | Téléverser un paquet de site et l’analyser pour import. |
transfer:execute | Démarrer, faire avancer, annuler et abandonner un import de site. |
admin | Appeler tous les outils core, y compris le transfert de site. Les outils plugin exigent toujours mcp:tools ou le scope spécifique au plugin. |
Le scope admin inclut transfer:export, transfer:analyze et transfer:execute. Chaque scope de transfert n’accorde que ses propres actions et exige le rôle administrateur. Pour qu’un client, par exemple un agent, analyse un paquet sans exporter ni importer, accordez transfer:analyze plutôt que admin.
La page de consentement du code d’autorisation permet à l’utilisateur de retirer des scopes demandés. EmDash intersecte aussi la demande avec les scopes enregistrés du client et le rôle de l’utilisateur, et refuse une octroi vide.
Exigences de rôle
Le tableau suivant indique le rôle minimum pour la capacité large. Les vérifications de propriété peuvent exiger un rôle supérieur lorsqu’un utilisateur agit sur le contenu d’un autre.
| Capacité | Rôle minimum |
|---|---|
| Lire contenu publié, médias, taxonomies, termes et menus | Subscriber |
| Lire brouillons, contenu planifié, corbeille, comparaisons et révisions | Contributor |
| Créer du contenu ou téléverser des médias | Contributor |
| Modifier ou publier son contenu et enregistrer des médias | Author |
| Gérer bylines, taxonomies, menus ou le contenu de tous les utilisateurs | Editor |
| Lire schémas ou paramètres | Editor |
| Modifier schémas ou paramètres, supprimer définitivement du contenu ou réparer l’usage média | Admin |
| Exporter ou importer l’ensemble du site | Admin |
Voir rôles utilisateur pour les définitions complètes.
Transport
Le serveur utilise HTTP Streamable sans état. Chaque requête est indépendante ; le serveur ne conserve ni session MCP ni connexion Server-Sent Events.
| Méthode | Point de terminaison | Comportement |
|---|---|---|
POST | /_emdash/api/mcp | Accepte initialisation JSON-RPC, listage d’outils et appels d’outils. |
GET | /_emdash/api/mcp | Renvoie 405 Method Not Allowed. |
DELETE | /_emdash/api/mcp | Renvoie 405 Method Not Allowed. |
Les réponses utilisent JSON-RPC 2.0. Appelez tools/list pour obtenir les schémas d’entrée et annotations MCP actuels avant de construire une requête d’outil.
Inventaire des outils
L’inventaire suivant correspond aux outils statiques renvoyés par tools/list. Le titre enregistré est inclus car les clients peuvent l’afficher à la place du nom de l’outil.
Outils de contenu
| 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 |
Outils byline
| 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 |
Outils de schéma
| 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 |
Outils média
| 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 |
Outil de recherche
| Tool | Registered title | Required scope |
|---|---|---|
search | Search Content | content:read |
Outils de taxonomie
| 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 |
Outils de menu
| 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 |
Outils de révision
| Tool | Registered title | Required scope |
|---|---|---|
revision_list | List Revisions | content:read |
revision_restore | Restore Revision | content:write |
Outils de paramètres
| Tool | Registered title | Required scope |
|---|---|---|
settings_get | Get Site Settings | settings:read |
settings_update | Update Site Settings | settings:manage |
Outils de transfert de site
transfer:* désigne l’un de transfer:export, transfer:analyze ou transfer:execute. Le scope admin satisfait toute exigence de ce tableau.
| 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 et site_import_start acceptent aussi un jeton sans le scope lorsqu’un admin approuve la demande. Pour l’opération qu’une demande approuvée démarre, site_export_status, site_import_status, site_import_resume et site_import_receipt acceptent le même jeton sans le scope.
Utiliser les schémas d’outils
tools/list renvoie pour chaque outil la description, le schéma d’entrée JSON et les annotations. Lisez ces métadonnées avant de construire un appel pour que votre client utilise les champs, valeurs autorisées et limites pris en charge par la version EmDash installée.
Par exemple, un client mettant à jour un article appelle d’abord content_get et conserve le _rev renvoyé. Il peut ensuite envoyer cette requête 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"
}
}
}
Le résultat est renvoyé en texte JSON dans le premier bloc de contenu. Un outil avec schéma de sortie peut aussi renvoyer la même valeur dans structuredContent.
Cycle de vie du contenu et bylines
La référence du cycle de vie du contenu définit l’état, la révision, les permissions, les conflits et le comportement des hooks partagés par MCP, REST, la CLI et le panneau d’admin.
content_get renvoie une valeur _rev opaque. Passez-la à content_update, content_publish, content_unpublish, content_schedule ou content_discard_draft. Une valeur obsolète provoque un conflit ; relisez l’élément avant de réessayer.
content_update est une mise à jour partielle : les champs omis conservent leurs valeurs actuelles. Mettre à jour un élément publié prépare un brouillon tandis que la version en ligne reste inchangée. Utilisez content_compare pour comparer les valeurs en ligne et brouillon, puis appelez content_publish pour publier le brouillon ou content_discard_draft pour le supprimer. content_delete déplace un élément vers la corbeille ; seul content_permanent_delete supprime définitivement un élément en corbeille.
Les bylines sont des crédits réutilisables d’auteur ou contributeur. byline_create peut créer un crédit invité ou lier une byline à un utilisateur CMS. Passez l’ID byline renvoyé dans l’entrée bylines acceptée par content_create et content_update. Supprimer une byline retire ce crédit du contenu et l’efface comme byline principale.
Les écritures MCP ne participent pas au verrou d’édition d’entrée de l’admin. La vérification _rev protège les opérations qui l’acceptent, mais d’autres outils d’écriture peuvent modifier une entrée pendant qu’un éditeur l’a ouverte.
Traductions
Les outils de traduction de contenu, byline, terme de taxonomie et menu renvoient chaque variante de locale du groupe de traduction concerné. Utilisez l’entrée translationOf de l’outil de création lorsque son schéma la fournit ; tools/list fait foi pour les champs requis.
content_translations accepte collection et ID ou slug de contenu. Les outils de traduction byline, terme et menu acceptent l’ID d’un enregistrement ou l’ID partagé du groupe de traduction. Un utilisateur sans accès aux brouillons ne voit que les traductions de contenu publié.
Schémas, médias, taxonomies et menus
Les outils de schéma modifient la structure de la base de données. Utilisez schema_get_collection avant de créer du contenu ou de modifier des champs ; il renvoie noms de champs, types, contraintes et règles de validation disponibles. La suppression de collections et champs retire le contenu stocké ou les valeurs de champ et est irréversible.
Utilisez media_upload pour envoyer des octets encodés en base64. Les téléversements sont soumis aux limites de taille et de type MIME configurées ; des octets identiques peuvent renvoyer un élément média existant avec deduplicated: true.
media_create confirme un téléversement en attente créé via POST /_emdash/api/media/upload-url. Téléversez le fichier avec l’URL signée renvoyée, puis appelez media_create depuis le même compte utilisateur avec le storageKey renvoyé. L’outil vérifie que le fichier stocké existe et correspond à la taille indiquée lors de la demande d’URL avant de le rendre disponible dans la bibliothèque média.
Les définitions de taxonomie décrivent la classification et les collections concernées ; les termes sont les valeurs individuelles assignées au contenu. Les termes hiérarchiques peuvent utiliser parentId, mais un parent doit appartenir à la même taxonomie et ne peut créer de cycle. Créer ou mettre à jour un terme avec parentId dans une taxonomie non hiérarchique renvoie VALIDATION_ERROR. Un terme avec des enfants doit les retirer ou les déplacer avant suppression.
menu_set_items remplace la liste complète des éléments d’un menu en une opération atomique. L’ordre du tableau devient l’ordre du menu. Le parentIndex d’un élément imbriqué pointe vers un élément antérieur du même tableau ; placez chaque parent avant ses enfants.
media_usage_repair peut traiter une collection ou toutes les collections et peut s’exécuter longtemps sur un grand site. Ses statuts complete, partial, failed et stale sont des réponses d’outil réussies. Inspectez le statut et les comptages renvoyés plutôt que de vous fier à isError ; authentification, validation et échecs d’exécution inattendus définissent isError: true.
Transfert de site
Les outils site_* exportent un site entier en paquet de site et importent un paquet dans un site vide. Ils démarrent et pilotent des opérations et renvoient des résumés bornés. Ils ne transportent jamais les octets du paquet, les médias, le contenu des enregistrements, les adresses e-mail des principals ni les URL de téléchargement. Téléchargez une exportation et téléversez un paquet pour import avec la CLI ou l’API REST, puis référencez l’opération par son ID.
Chaque outil de transfert exige le rôle Admin. Le rôle est vérifié avant le scope ; un appelant non admin reçoit INSUFFICIENT_PERMISSIONS et aucune demande d’approbation n’est créée.
Export
site_export_start accepte comments (par défaut true) et renvoie la nouvelle opération. site_export_status exécute une étape d’export bornée à chaque appel et signale l’opération et nextRequestInMs. Rappelez après ce délai jusqu’à ce que nextRequestInMs soit null. Passez advance: false pour lire le statut sans exécuter d’étape. Une fois l’export terminé, le résultat inclut aussi totals : comptages d’enregistrements par type, nombre et octets média, et nombre et octets de fichiers du paquet.
Import
Téléversez d’abord le paquet. emdash site import <file> --analyze de la CLI téléverse, analyse et affiche l’ID d’opération.
site_import_analyze exécute une étape d’analyse bornée par appel. Répétez jusqu’à ce que nextRequestInMs soit null ; le résultat inclut alors un résumé de plan avec packageDigest, planDigest, executable, comptages, tailles, principals, décisions, transformations, avertissements et bloqueurs. Chaque transformation est listée par son code, le kind d’enregistrement le cas échéant, et un count, sans les ID ou valeurs concernés. Principals, avertissements et bloqueurs listent au plus 50 éléments chacun, avec le total dans total. Les principals sont listés sans adresses e-mail, avec les ID utilisateur cible suggérés et mappés actuellement. Passez decisions pour mapper les principals aux ID utilisateur cible (ou null) et choisir titre et slogan du paquet ou de la cible. Chaque changement produit un nouveau planDigest.
site_import_start prend l’ID d’opération et les packageDigest et planDigest du plan le plus récent. Le plan ne doit avoir aucun bloqueur. L’outil a destructiveHint: true : une fois démarré, l’import écrit sur le site et bloque les autres écritures jusqu’à achèvement ou abandon par un administrateur. Montrez le plan à l’utilisateur et obtenez sa confirmation avant l’appel.
site_import_resume exécute une étape d’import bornée et signale l’opération et nextRequestInMs. Appelez jusqu’à ce que nextRequestInMs soit null ; la répétition après déconnexion est sûre. site_import_status signale l’opération et les comptages de fichiers téléversés sans faire avancer l’import. site_import_receipt renvoie le reçu complet, y compris receiptDigest, une fois l’import terminé.
Pendant qu’un import s’exécute, et après échec ou annulation jusqu’à abandon, tout autre outil capable d’écrire échoue avec TRANSFER_IMPORT_IN_PROGRESS. Cela inclut les outils plugin. Les outils annotés readOnlyHint: true et les huit outils site_* continuent de fonctionner, et initialize et tools/list ne sont jamais bloqués. Pendant l’activation d’usage média, les outils d’écriture échouent de la même façon avec MEDIA_USAGE_ACTIVATION_IN_PROGRESS.
Les résumés d’opération incluent id, kind, state, stage, progress, packageDigest, planDigest, error ({ code } ou null) et horodatages. progress est { done, total } étapes, plus records écrits par une exportation jusqu’ici, et bytesDone et bytesTotal une fois connus. Les outils MCP ne peuvent pas annuler ni abandonner un import ; utilisez l’API REST.
Approbations
Un jeton avec admin ou le scope de transfert requis ne demande jamais d’approbation. Pour un jeton sans l’un ni l’autre, par exemple un agent avec seulement transfer:analyze, site_export_start et site_import_start s’exécutent lorsqu’un administrateur approuve la demande :
- Le premier appel sans scope crée une demande d’approbation en attente et échoue avec
TRANSFER_APPROVAL_REQUIRED. Le texte du message et_meta.detailsportentapprovalIdet sonexpiresAt. Un nouvel appel avec les mêmes arguments et sansapprovalIdrenvoie la même demande ouverte. - Un administrateur approuve sous Demandes d’approbation dans Paramètres → Transfer, ou via le point de terminaison d’approbation réservé à la session de l’API REST. Les jetons API ne peuvent pas approuver.
- Le client répète l’appel avec les mêmes arguments et l’
approvalId. L’approbation est consommée lorsque cet appel démarre l’opération. Si l’opération ne démarre pas, le client peut réessayer avec le mêmeapprovalIdjusqu’à expiration.
Une demande est liée à l’utilisateur, au jeton, à l’action et aux arguments exacts : options d’export, ou ID d’opération d’import et les deux digests. Une demande en attente expire 15 minutes après création, une approuvée 15 minutes après approbation. Un appel avec d’autres arguments ou un autre jeton, ou avec approbation refusée, expirée ou utilisée, échoue avec TRANSFER_APPROVAL_INVALID.
site_import_start vérifie digests, état de l’opération et bloqueurs du plan avant de créer une demande, afin qu’un administrateur ne soit sollicité que pour un import exécutable. Une approbation exige un ID de jeton ; un appelant sans en reçoit INSUFFICIENT_SCOPE.
Après un appel approuvé qui démarre une opération, le même utilisateur et jeton peuvent appeler site_export_status, ou site_import_status, site_import_resume et site_import_receipt, pour cette opération sans le scope.
Outils plugin
Un administrateur doit activer la surface MCP de chaque plugin. Les outils activés apparaissent dans tools/list comme <pluginId>__<localName> et exigent mcp:tools ou mcp:tools:<pluginId> pour les appels authentifiés par jeton. EmDash vérifie aussi la permission déclarée par la route plugin et enregistre plugin, outil, route et acteur dans le journal d’audit.
Les outils plugin étant spécifiques à l’installation, ils ne font pas partie de l’inventaire statique ci-dessus.
Découverte OAuth
Les clients MCP découvrent le serveur d’autorisation via les métadonnées de ressource protégée :
GET /.well-known/oauth-protected-resource
La réponse identifie /_emdash/api/mcp comme ressource protégée et lie au serveur d’autorisation. Les clients lisent ensuite ses métadonnées à :
GET /.well-known/oauth-authorization-server/_emdash
Ce document fournit les points de terminaison actuels d’autorisation, de jeton, d’enregistrement et d’autorisation d’appareil, les scopes pris en charge, les types de grant et la méthode PKCE S256. Utilisez les valeurs découvertes plutôt que de coder en dur les routes du protocole OAuth.
Une requête MCP non authentifiée renvoie 401 avec l’URL de découverte :
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"
Erreurs
Un échec d’outil a isError: true. Le premier bloc texte commence par un code stable, et _meta.code le répète pour les clients lisant les métadonnées structurées :
{
"content": [{ "type": "text", "text": "[NOT_FOUND] Collection 'articles' not found" }],
"isError": true,
"_meta": { "code": "NOT_FOUND" }
}
Les échecs d’authentification utilisent des codes tels que INSUFFICIENT_SCOPE et INSUFFICIENT_PERMISSIONS. Les échecs de transport utilisent le code d’erreur interne JSON-RPC -32603 et n’exposent pas l’exception sous-jacente.