EmDash espone un server Model Context Protocol (MCP) integrato su /_emdash/api/mcp. I client MCP lo usano per leggere e gestire contenuti, byline, schemi, media, tassonomie, menu, revisioni e impostazioni, e per esportare o importare l’intero sito.
Autenticazione
L’endpoint MCP richiede un token Bearer. EmDash supporta questi flussi di token:
| Metodo | Uso |
|---|---|
| OAuth 2.1 Authorization Code with Proof Key for Code Exchange (PKCE) | Client MCP interattivi. L’utente approva in un browser gli scope richiesti. |
| Personal access token | Accesso di lunga durata per un client o un’automazione. I token usano il prefisso ec_pat_ e si creano nell’admin. |
| OAuth 2.0 Device Authorization Grant | Client da riga di comando che chiedono all’utente di approvare un codice in un browser. emdash login usa questo flusso. |
I cookie di sessione non autenticano l’endpoint MCP.
Scope
I token OAuth e i token di accesso personali limitano quali strumenti un client può chiamare. Il ruolo dell’utente viene verificato separatamente, quindi uno scope non concede mai un permesso che l’utente non ha.
| Scope | Accesso |
|---|---|
content:read | Leggere e cercare contenuti, byline, tassonomie, termini, menu e revisioni. I contenuti di tipo bozza richiedono anche il permesso content:read_drafts dell’utente. |
content:write | Creare e modificare contenuti, byline e revisioni. Concede anche taxonomies:manage e menus:manage per compatibilità con i token esistenti. |
media:read | Leggere i record dei media. |
media:write | Caricare, registrare, aggiornare ed eliminare media. |
schema:read | Leggere collezioni e campi. |
schema:write | Creare, aggiornare ed eliminare collezioni e campi. |
taxonomies:manage | Creare, aggiornare ed eliminare definizioni di tassonomie e termini. |
menus:manage | Creare, aggiornare ed eliminare menu e voci di menu. |
settings:read | Leggere le impostazioni del sito. |
settings:manage | Aggiornare le impostazioni del sito. |
mcp:tools | Chiamare gli strumenti MCP esposti da qualsiasi plugin abilitato. |
mcp:tools:<pluginId> | Chiamare gli strumenti MCP esposti da un singolo plugin abilitato. |
transfer:export | Esportare l’intero sito come pacchetto del sito e scaricarlo. |
transfer:analyze | Caricare un pacchetto del sito e analizzarlo per l’importazione. |
transfer:execute | Avviare, far avanzare, annullare e abbandonare un’importazione del sito. |
admin | Chiamare tutti gli strumenti core, compresi quelli di trasferimento del sito. Gli strumenti dei plugin richiedono comunque mcp:tools o lo scope specifico del plugin. |
Lo scope admin include transfer:export, transfer:analyze e transfer:execute. Ogni scope di trasferimento concede solo le proprie azioni e richiede il ruolo di amministratore. Per permettere a un client, come un agente, di analizzare un pacchetto del sito senza esportare né importare, concedi transfer:analyze invece di admin.
La pagina di consenso del codice di autorizzazione permette all’utente di rimuovere gli scope richiesti. EmDash interseca inoltre la richiesta con gli scope registrati del client e con il ruolo dell’utente, e rifiuta una concessione vuota.
Requisiti di ruolo
La tabella seguente mostra il ruolo minimo per ciascuna capacità generale. I controlli di proprietà possono richiedere un ruolo più alto quando un utente agisce sui contenuti di un altro utente.
| Capacità | Ruolo minimo |
|---|---|
| Leggere contenuti pubblicati, media, tassonomie, termini e menu | Subscriber |
| Leggere bozze, contenuti pianificati, cestino, confronti e revisioni | Contributor |
| Creare contenuti o caricare media | Contributor |
| Modificare o pubblicare i propri contenuti e registrare media | Author |
| Gestire byline, tassonomie, menu o i contenuti di tutti gli utenti | Editor |
| Leggere schemi o impostazioni | Editor |
| Modificare schemi o impostazioni, eliminare definitivamente contenuti o riparare l’uso dei media | Admin |
| Esportare o importare l’intero sito | Admin |
Consulta i ruoli utente per le definizioni complete dei ruoli.
Trasporto
Il server usa HTTP Streamable senza stato. Ogni richiesta è indipendente; il server non mantiene una sessione MCP né una connessione Server-Sent Events.
| Metodo | Endpoint | Comportamento |
|---|---|---|
POST | /_emdash/api/mcp | Accetta l’inizializzazione JSON-RPC, l’elenco degli strumenti e le chiamate agli strumenti. |
GET | /_emdash/api/mcp | Restituisce 405 Method Not Allowed. |
DELETE | /_emdash/api/mcp | Restituisce 405 Method Not Allowed. |
Le risposte usano JSON-RPC 2.0. Chiama tools/list per ottenere gli schemi di input e le annotazioni MCP correnti prima di costruire una richiesta a uno strumento.
Inventario degli strumenti
L’inventario seguente corrisponde agli strumenti statici restituiti da tools/list. Il titolo registrato è incluso perché i client possono mostrarlo al posto del nome dello strumento.
Strumenti per i contenuti
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
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 |
Strumenti per le byline
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
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 |
Strumenti per gli schemi
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
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 |
Strumenti per i media
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
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 |
Strumento di ricerca
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
search | Search Content | content:read |
Strumenti per le tassonomie
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
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 |
Strumenti per i menu
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
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 |
Strumenti per le revisioni
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
revision_list | List Revisions | content:read |
revision_restore | Restore Revision | content:write |
Strumenti per le impostazioni
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
settings_get | Get Site Settings | settings:read |
settings_update | Update Site Settings | settings:manage |
Strumenti di trasferimento del sito
transfer:* indica uno qualsiasi tra transfer:export, transfer:analyze o transfer:execute. Lo scope admin soddisfa tutti i requisiti di questa tabella.
| Strumento | Titolo registrato | Scope richiesto |
|---|---|---|
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 e site_import_start accettano anche un token privo dello scope quando un amministratore approva la richiesta. Per l’operazione avviata da una richiesta approvata, site_export_status, site_import_status, site_import_resume e site_import_receipt accettano poi lo stesso token privo dello scope.
Usare gli schemi degli strumenti
tools/list restituisce la descrizione, lo schema JSON di input e le annotazioni di ogni strumento. Leggi questi metadati prima di costruire una chiamata, in modo che il tuo client usi i campi, i valori consentiti e i limiti supportati dalla versione di EmDash installata.
Ad esempio, un client che aggiorna un articolo chiama prima content_get e conserva il _rev restituito. Può quindi inviare questa richiesta 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"
}
}
}
Il risultato viene restituito come testo JSON nel primo blocco di contenuto. Uno strumento con uno schema di output può restituire lo stesso valore anche in structuredContent.
Ciclo di vita dei contenuti e byline
Il riferimento al ciclo di vita dei contenuti definisce il comportamento di stati, revisioni, permessi, conflitti e hook condiviso da MCP, REST, dalla CLI e dal pannello di amministrazione.
content_get restituisce un valore _rev opaco. Passalo a content_update, content_publish, content_unpublish, content_schedule o content_discard_draft. Un valore obsoleto restituisce un conflitto, quindi rileggi l’elemento prima di riprovare.
content_update è un aggiornamento parziale: i campi omessi mantengono i valori attuali. Aggiornare un elemento pubblicato prepara una bozza mentre la versione live resta invariata. Usa content_compare per rivedere i valori live e quelli della bozza, quindi chiama content_publish per rendere live la bozza o content_discard_draft per rimuoverla. content_delete sposta un elemento nel cestino; solo content_permanent_delete rimuove definitivamente un elemento che si trova nel cestino.
Le byline sono crediti riutilizzabili di autore o collaboratore. byline_create può creare un credito per un ospite o collegare una byline a un utente del CMS. Un utente può avere una sola byline per locale. Una traduzione creata con translationOf mantiene l’utente della byline di origine, a meno che tu non passi userId; passa null per lasciare la traduzione senza collegamento. Passa l’ID della byline restituito nell’input bylines accettato da content_create e content_update. Eliminare una byline rimuove quel credito dai contenuti e la cancella come byline principale.
Finché un altro utente detiene il blocco di modifica di una voce perché l’ha aperta nell’admin, content_update, content_delete, content_publish, content_unpublish, content_schedule, content_unschedule, content_discard_draft e revision_restore falliscono con ENTRY_LOCKED, e l’errore indica chi lo detiene. Rileggere l’elemento non elimina il rifiuto. Passa overrideLock: true per scrivere comunque.
Traduzioni
Gli strumenti di traduzione per contenuti, byline, termini di tassonomia e menu restituiscono ogni variante di locale del gruppo di traduzione interessato. Usa l’input translationOf dello strumento di creazione quando il suo schema ne prevede uno; per i campi obbligatori fa fede tools/list.
content_translations accetta una collezione e un ID o uno slug di contenuto. Gli strumenti di traduzione per byline, termini di tassonomia e menu accettano l’ID di un singolo record oppure l’ID condiviso del gruppo di traduzione. Un utente senza accesso alle bozze vede solo le traduzioni dei contenuti pubblicati.
Schemi, media, tassonomie e menu
Gli strumenti per gli schemi modificano la struttura del database. Usa schema_get_collection prima di creare contenuti o modificare campi; restituisce i nomi dei campi, i tipi, i vincoli e le regole di validazione disponibili. L’eliminazione di collezioni e campi rimuove i contenuti o i valori dei campi archiviati e non può essere annullata.
Usa media_upload per inviare byte codificati in base64. I caricamenti sono soggetti ai limiti di dimensione e di tipo MIME configurati, e byte identici possono restituire un elemento multimediale esistente con deduplicated: true.
media_create conferma un caricamento in sospeso creato tramite POST /_emdash/api/media/upload-url. Carica il file con l’URL firmato restituito, poi chiama media_create dallo stesso account utente con il storageKey restituito. Lo strumento verifica che il file archiviato esista e corrisponda alla dimensione indicata quando è stato richiesto l’URL di caricamento, prima di renderlo disponibile nella libreria multimediale.
Le definizioni di tassonomia descrivono la classificazione e le collezioni a cui si applica; i termini sono i singoli valori assegnati ai contenuti. I termini gerarchici possono usare parentId, ma il genitore deve appartenere alla stessa tassonomia e non può creare un ciclo. Creare o aggiornare un termine con parentId in una tassonomia non gerarchica restituisce VALIDATION_ERROR. Un termine con figli deve avere quei figli rimossi o spostati prima di essere eliminato.
menu_set_items sostituisce l’intero elenco di voci di un menu in un’unica operazione atomica. L’ordine dell’array diventa l’ordine del menu. Il parentIndex di una voce annidata punta a una voce precedente nello stesso array, quindi posiziona ogni genitore prima dei suoi figli.
media_usage_repair può elaborare una collezione o tutte le collezioni e può durare a lungo su un sito grande. I suoi stati complete, partial, failed e stale sono risposte riuscite dello strumento. Controlla lo stato e i conteggi restituiti invece di affidarti a isError; i fallimenti di autenticazione, validazione ed esecuzione imprevisti impostano isError: true.
Trasferimento del sito
Gli strumenti site_* esportano un intero sito come pacchetto del sito e importano un pacchetto in un sito vuoto. Avviano e guidano le operazioni e restituiscono riepiloghi limitati. Non trasportano mai i byte del pacchetto, i media, il contenuto dei record, gli indirizzi e-mail dei principal né gli URL di download. Scarica un’esportazione e carica un pacchetto per l’importazione con la CLI o con l’API REST, poi fai riferimento all’operazione tramite il suo ID.
Ogni strumento di trasferimento richiede il ruolo Admin. Il ruolo viene verificato prima dello scope, quindi un chiamante che non è amministratore riceve INSUFFICIENT_PERMISSIONS e non viene creata alcuna richiesta di approvazione.
Esportazione
site_export_start accetta comments (predefinito true) e restituisce la nuova operazione. site_export_status esegue un passaggio di esportazione limitato a ogni chiamata e riporta l’operazione e nextRequestInMs. Chiamalo di nuovo dopo quel ritardo finché nextRequestInMs non è null. Passa advance: false per leggere lo stato senza eseguire un passaggio. Quando l’esportazione è completa, il risultato include anche totals: conteggi dei record per tipo, numero e byte dei media, e numero e byte dei file del pacchetto.
Importazione
Carica prima il pacchetto. Il comando emdash site import <file> --analyze della CLI lo carica, lo analizza e stampa l’ID dell’operazione.
site_import_analyze esegue un passaggio di analisi limitato a ogni chiamata. Ripetilo finché nextRequestInMs non è null; il risultato include quindi un riepilogo del piano con packageDigest, planDigest, executable, conteggi, dimensioni, principal, decisioni, trasformazioni, avvisi e bloccanti. Ogni trasformazione è elencata con il suo code, il kind del record quando ne ha uno e un count, senza gli ID o i valori a cui si applica. Principal, avvisi e bloccanti elencano al massimo 50 elementi ciascuno, con il conteggio completo in total. I principal sono elencati senza indirizzi e-mail, con gli ID utente di destinazione suggeriti e quelli attualmente mappati. Passa decisions per mappare i principal a ID utente di destinazione (o null) e per scegliere il titolo e lo slogan del pacchetto o della destinazione. Ogni modifica produce un nuovo planDigest.
site_import_start riceve l’ID dell’operazione e il packageDigest e il planDigest del piano più recente. Il piano non deve avere bloccanti. Lo strumento ha destructiveHint: true: una volta avviata, l’importazione scrive nel sito e blocca le altre scritture finché non termina o un amministratore la abbandona. Mostra il piano all’utente e ottieni la sua conferma prima di chiamarlo.
site_import_resume esegue un passaggio di importazione limitato e riporta l’operazione e nextRequestInMs. Chiamalo finché nextRequestInMs non è null; è sicuro ripeterlo dopo una disconnessione. site_import_status riporta l’operazione e il numero di file caricati senza far avanzare l’importazione. site_import_receipt restituisce la ricevuta completa, incluso receiptDigest, una volta completata l’importazione.
Mentre un’importazione è in esecuzione, e dopo che è fallita o è stata annullata fino a quando non viene abbandonata, ogni altro strumento che può scrivere fallisce con TRANSFER_IMPORT_IN_PROGRESS. Questo include gli strumenti dei plugin. Gli strumenti annotati con readOnlyHint: true e gli otto strumenti site_* continuano a funzionare, e initialize e tools/list non vengono mai bloccati. Durante l’attivazione dell’uso dei media, gli strumenti di scrittura falliscono allo stesso modo con MEDIA_USAGE_ACTIVATION_IN_PROGRESS.
I riepiloghi delle operazioni includono id, kind, state, stage, progress, packageDigest, planDigest, error ({ code } o null) e i timestamp. progress è { done, total } passaggi, più records, i record che un’esportazione ha scritto finora, e bytesDone e bytesTotal, una volta noti. Gli strumenti MCP non possono annullare né abbandonare un’importazione; usa l’API REST.
Approvazioni
Un token con admin o con lo scope di trasferimento necessario non chiede mai l’approvazione. Per un token privo di entrambi, come quello di un agente a cui è stato concesso solo transfer:analyze, site_export_start e site_import_start vengono eseguiti quando un amministratore approva la richiesta:
- La prima chiamata senza lo scope crea una richiesta di approvazione in sospeso e fallisce con
TRANSFER_APPROVAL_REQUIRED. Il testo del messaggio e_meta.detailscontengono l’approvalIde il suoexpiresAt. Chiamare di nuovo con gli stessi argomenti e senzaapprovalIdrestituisce la stessa richiesta aperta. - Un amministratore approva la richiesta in Approval requests, sotto Settings → Transfer, oppure con l’endpoint di approvazione solo sessione dell’API REST. I token API non possono approvare le richieste.
- Il client ripete la chiamata con gli stessi argomenti e l’
approvalId. L’approvazione viene consumata quando quella chiamata avvia l’operazione. Se l’operazione non riesce ad avviarsi, il client può riprovare con lo stessoapprovalIdfino alla scadenza.
Una richiesta è legata all’utente, al token, all’azione e agli argomenti esatti: le opzioni di esportazione, oppure l’ID dell’operazione di importazione e i due digest. Una richiesta in sospeso scade 15 minuti dopo la creazione, e una approvata 15 minuti dopo l’approvazione. Una chiamata con argomenti diversi o con un altro token, oppure con un’approvazione negata, scaduta o già usata, fallisce con TRANSFER_APPROVAL_INVALID.
site_import_start verifica i digest, lo stato dell’operazione e i bloccanti del piano prima di creare una richiesta, così a un amministratore viene chiesto di approvare solo un’importazione che può essere eseguita. Un’approvazione richiede un ID del token, quindi un chiamante che non ne ha uno riceve INSUFFICIENT_SCOPE.
Dopo che una chiamata approvata ha avviato un’operazione, lo stesso utente e lo stesso token possono chiamare site_export_status, oppure site_import_status, site_import_resume e site_import_receipt, per quell’operazione senza lo scope.
Strumenti dei plugin
Un amministratore deve abilitare la superficie MCP di ciascun plugin. Gli strumenti abilitati compaiono in tools/list come <pluginId>__<localName> e richiedono mcp:tools o mcp:tools:<pluginId> per le chiamate autenticate con token. EmDash verifica inoltre il permesso dichiarato dalla route del plugin e registra plugin, strumento, route e attore nel log di audit.
Poiché gli strumenti dei plugin dipendono dall’installazione, non fanno parte dell’inventario statico qui sopra.
Discovery OAuth
I client MCP scoprono il server di autorizzazione a partire dai metadati della risorsa protetta:
GET /.well-known/oauth-protected-resource
La risposta identifica /_emdash/api/mcp come risorsa protetta e rimanda al server di autorizzazione. I client leggono poi i suoi metadati a:
GET /.well-known/oauth-authorization-server/_emdash
Quel documento fornisce gli endpoint correnti di autorizzazione, token, registrazione e autorizzazione del dispositivo, gli scope e i tipi di grant supportati e il metodo PKCE S256. Usa i valori scoperti invece di codificare in modo fisso le route del protocollo OAuth.
Una richiesta MCP non autenticata restituisce una risposta 401 con l’URL di discovery:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"
Errori
Un errore di uno strumento ha isError: true. Il primo blocco di testo inizia con un codice stabile, e _meta.code lo ripete per i client che leggono i metadati strutturati:
{
"content": [{ "type": "text", "text": "[NOT_FOUND] Collection 'articles' not found" }],
"isError": true,
"_meta": { "code": "NOT_FOUND" }
}
Un rifiuto dovuto al blocco di modifica di una voce riporta chi lo detiene in _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"
}
}
}
Gli errori di autenticazione usano codici come INSUFFICIENT_SCOPE e INSUFFICIENT_PERMISSIONS. Gli errori di trasporto usano il codice di errore interno di JSON-RPC -32603 e non espongono l’eccezione sottostante.