EmDash incluye un servidor Model Context Protocol (MCP) integrado en /_emdash/api/mcp que expone operaciones de gestión de contenido como herramientas para asistentes de IA.
Esta página cubre los detalles del protocolo: autenticación, transporte, especificaciones de herramientas, descubrimiento OAuth y manejo de errores.
Autenticación
El servidor MCP soporta tres métodos de autenticación:
| Método | Cómo funciona |
|---|---|
| OAuth 2.1 Authorization Code + PKCE | Flujo estándar para clientes MCP. El usuario aprueba los scopes en el navegador. |
| Token de acceso personal (PAT) | Tokens de larga duración ec_pat_* creados en el panel de administración. |
| Device Flow | Flujo estilo CLI donde apruebas un código en el navegador. Usado por emdash login. |
Las cookies de sesión (de la UI de administración) también funcionan pero no son prácticas para clientes MCP externos.
Scopes
Los tokens tienen scopes para limitar qué operaciones puede realizar un cliente. Los scopes se solicitan durante la autorización OAuth y se aplican en cada llamada a herramientas.
| Scope | Otorga acceso a |
|---|---|
content:read | Listar, obtener, comparar y buscar contenido. Listar términos de taxonomía y menús. |
content:write | Crear, actualizar, eliminar, publicar, despublicar, programar, duplicar y restaurar contenido. Crear términos de taxonomía. |
media:read | Listar y obtener elementos de medios. |
media:write | Actualizar y eliminar metadatos de medios. |
schema:read | Listar colecciones y obtener esquemas de colección. |
schema:write | Crear y eliminar colecciones y campos. |
admin | Acceso completo a todas las operaciones. |
El scope admin otorga acceso a todo. La autenticación basada en sesión (sin token) también tiene acceso completo según el rol del usuario.
Requisitos de rol
Además de los scopes, algunas herramientas requieren un rol RBAC mínimo:
| Operación | Rol mínimo |
|---|---|
| Operaciones de contenido | Sin mínimo (los scopes controlan el acceso) |
| Lectura de esquema | Editor (40) |
| Escritura de esquema | Admin (50) |
Consulta la guía de autenticación para las definiciones de roles.
Transporte
El servidor usa el transporte Streamable HTTP en modo sin estado. Cada solicitud es independiente — no hay sesiones ni conexiones de larga duración.
POST /_emdash/api/mcp— Enviar llamadas de herramientas JSON-RPCGET /_emdash/api/mcp— Devuelve 405 (sin SSE en modo sin estado)DELETE /_emdash/api/mcp— Devuelve 405 (sin sesión que cerrar)
Las respuestas siguen el formato JSON-RPC 2.0. Los errores usan códigos de error JSON-RPC estándar, con códigos específicos de MCP para fallos de scope y permisos.
Herramientas
El servidor expone 33 herramientas en siete dominios. Cada herramienta devuelve resultados como contenido de texto JSON, o un mensaje de error con isError: true en caso de fallo.
Herramientas de contenido
content_list
Lista elementos de contenido en una colección con filtrado y paginación opcionales.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección (ej. posts, pages) |
status | string | No | Filtro: draft, published o scheduled |
limit | integer | No | Máximo de elementos a devolver (1-100, predeterminado 50) |
cursor | string | No | Cursor de paginación de una respuesta anterior |
orderBy | string | No | Campo para ordenar (ej. created_at, updated_at) |
order | string | No | Dirección de orden: asc o desc (predeterminado desc) |
locale | string | No | Filtrar por locale (ej. en, fr). Solo relevante con i18n. |
Scope: content:read | Solo lectura: Sí
content_get
Obtiene un elemento de contenido por ID o slug. Devuelve todos los valores de campo, metadatos y un token _rev para concurrencia optimista.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento (ULID) o slug |
locale | string | No | Locale para búsqueda por slug. Los IDs son globalmente únicos. |
Scope: content:read | Solo lectura: Sí
content_create
Crea un nuevo elemento de contenido. El objeto data debe contener valores de campo que coincidan con el esquema de la colección — usa schema_get_collection para verificar qué campos están disponibles. Los elementos se crean como draft por defecto.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
data | object | Sí | Valores de campo como pares clave-valor |
slug | string | No | Slug de URL (se genera automáticamente del título si se omite) |
status | string | No | Estado inicial: draft o published (predeterminado draft) |
locale | string | No | Locale para este contenido (predeterminado: el del sitio) |
translationOf | string | No | ID del elemento del que este es una traducción |
Scope: content:write
content_update
Actualiza un elemento de contenido existente. Solo incluye los campos que quieras cambiar — los campos no especificados se dejan sin cambios.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
data | object | No | Valores de campo a actualizar |
slug | string | No | Nuevo slug de URL |
status | string | No | Nuevo estado: draft o published |
_rev | string | No | Token de revisión de content_get para detección de conflictos |
Scope: content:write
content_delete
Eliminación suave de un elemento de contenido moviéndolo a la papelera. Usa content_restore para deshacer, o content_permanent_delete para eliminarlo permanentemente.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
Scope: content:write | Destructivo: Sí
content_restore
Restaura un elemento de contenido eliminado de la papelera.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
Scope: content:write
content_permanent_delete
Elimina permanente e irreversiblemente un elemento de la papelera. El elemento debe estar en la papelera previamente.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
Scope: content:write | Destructivo: Sí
content_publish
Publica un elemento de contenido, haciéndolo visible en el sitio. Crea una revisión publicada a partir del borrador actual. Las ediciones posteriores crean un nuevo borrador sin afectar la versión en vivo hasta que se republique.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
Scope: content:write
content_unpublish
Revierte un elemento publicado a estado de borrador. Ya no será visible en el sitio en vivo pero su contenido se preserva.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
Scope: content:write
content_schedule
Programa un elemento de contenido para publicación futura. Se publicará automáticamente en la fecha/hora especificada.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
scheduledAt | string | Sí | Fecha y hora ISO 8601 (ej. 2026-06-01T09:00:00Z) |
Scope: content:write
content_compare
Compara la versión publicada (en vivo) de un elemento de contenido con su borrador actual. Devuelve ambas versiones y un flag indicando si hay cambios.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
Scope: content:read | Solo lectura: Sí
content_discard_draft
Descarta el borrador actual y revierte a la última versión publicada. Solo funciona en elementos que han sido publicados al menos una vez.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
Scope: content:write | Destructivo: Sí
content_list_trashed
Lista los elementos de contenido eliminados en la papelera de una colección.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
limit | integer | No | Máximo de elementos (1-100, predeterminado 50) |
cursor | string | No | Cursor de paginación |
Scope: content:read | Solo lectura: Sí
content_duplicate
Crea una copia de un elemento de contenido existente. El duplicado se crea como borrador con “(Copy)” añadido al título y un slug generado automáticamente.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID o slug del elemento a duplicar |
Scope: content:write
content_translations
Obtiene todas las variantes de locale de un elemento de contenido. Devuelve el grupo de traducción y un resumen de cada versión por locale. Solo relevante cuando i18n está habilitado.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
Scope: content:read | Solo lectura: Sí
Herramientas de esquema
schema_list_collections
Lista todas las colecciones de contenido definidas en el CMS. Devuelve slug, etiqueta, funciones soportadas y marcas de tiempo.
Sin parámetros.
Scope: schema:read | Rol mínimo: Editor | Solo lectura: Sí
schema_get_collection
Obtiene información detallada sobre una colección incluyendo todas las definiciones de campo. Los campos describen el modelo de datos: nombre, tipo, restricciones y reglas de validación. Úsalo para entender qué esperan content_create y content_update.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
slug | string | Sí | Slug de la colección (ej. posts) |
Scope: schema:read | Rol mínimo: Editor | Solo lectura: Sí
schema_create_collection
Crea una nueva colección de contenido. Esto crea una tabla de base de datos y una definición de esquema. El slug debe ser alfanumérico en minúsculas con guiones bajos, comenzando con una letra.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
slug | string | Sí | Identificador único (/^[a-z][a-z0-9_]*$/) |
label | string | Sí | Nombre para mostrar (plural, ej. “Blog Posts”) |
labelSingular | string | No | Nombre singular para mostrar |
description | string | No | Descripción de esta colección |
icon | string | No | Nombre del icono para la UI de admin |
supports | string[] | No | Funciones: drafts, revisions, preview, scheduling, search (predeterminado: ['drafts', 'revisions']) |
Scope: schema:write | Rol mínimo: Admin
schema_delete_collection
Elimina una colección y su tabla de base de datos. Esto es irreversible y elimina todo el contenido de la colección.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
slug | string | Sí | Slug de la colección a eliminar |
force | boolean | No | Forzar eliminación aunque la colección tenga contenido |
Scope: schema:write | Rol mínimo: Admin | Destructivo: Sí
schema_create_field
Añade un nuevo campo al esquema de una colección. Esto añade una columna a la tabla de base de datos.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
slug | string | Sí | Identificador del campo (/^[a-z][a-z0-9_]*$/) |
label | string | Sí | Nombre para mostrar |
type | string | Sí | Tipo de dato (ver abajo) |
required | boolean | No | Si el campo es obligatorio |
unique | boolean | No | Si los valores deben ser únicos |
defaultValue | any | No | Valor predeterminado para nuevos elementos |
validation | object | No | Restricciones: min, max, minLength, maxLength, pattern, options |
options | object | No | Configuración de widget: collection (para referencias), rows (para textarea) |
searchable | boolean | No | Incluir en el índice de búsqueda de texto completo |
translatable | boolean | No | Si este campo es traducible (predeterminado true) |
Tipos de campo: string, text, number, integer, boolean, datetime, select, multiSelect, portableText, image, file, reference, json, slug.
Para los tipos select y multiSelect, proporciona los valores permitidos en validation.options.
Scope: schema:write | Rol mínimo: Admin
schema_delete_field
Elimina un campo de una colección. Esto elimina la columna y todos los datos de ese campo. Irreversible.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
fieldSlug | string | Sí | Slug del campo a eliminar |
Scope: schema:write | Rol mínimo: Admin | Destructivo: Sí
Herramientas de medios
media_list
Lista archivos de medios subidos con filtrado opcional por tipo MIME y paginación.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
mimeType | string | No | Filtrar por prefijo de tipo MIME (ej. image/, application/pdf) |
limit | integer | No | Máximo de elementos (1-100, predeterminado 50) |
cursor | string | No | Cursor de paginación |
Scope: media:read | Solo lectura: Sí
media_get
Obtiene los detalles de un archivo de medios por ID. Devuelve metadatos incluyendo nombre de archivo, tipo MIME, tamaño, dimensiones, texto alternativo y URL.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id | string | Sí | ID del elemento de medios |
Scope: media:read | Solo lectura: Sí
media_update
Actualiza los metadatos de un archivo de medios subido. El archivo en sí no se puede cambiar.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id | string | Sí | ID del elemento de medios |
alt | string | No | Texto alternativo para accesibilidad |
caption | string | No | Texto de leyenda |
width | integer | No | Ancho de la imagen en píxeles |
height | integer | No | Alto de la imagen en píxeles |
Scope: media:write
media_delete
Elimina permanentemente un archivo de medios. Elimina el registro de base de datos y el archivo del almacenamiento. El contenido que haga referencia a este medio tendrá referencias rotas.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id | string | Sí | ID del elemento de medios |
Scope: media:write | Destructivo: Sí
Herramienta de búsqueda
search
Búsqueda de texto completo en colecciones de contenido. Las colecciones deben tener search en su lista supports y los campos deben estar marcados como searchable.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
query | string | Sí | Texto de consulta de búsqueda |
collections | string[] | No | Limitar búsqueda a slugs de colección específicos |
locale | string | No | Filtrar resultados por locale |
limit | integer | No | Máximo de resultados (1-50, predeterminado 20) |
Scope: content:read | Solo lectura: Sí
Herramientas de taxonomía
taxonomy_list
Lista todas las definiciones de taxonomía (ej. categorías, etiquetas). Devuelve nombre, etiqueta, si es jerárquica y colecciones asociadas.
Sin parámetros.
Scope: content:read | Solo lectura: Sí
taxonomy_list_terms
Lista los términos de una taxonomía con paginación.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
taxonomy | string | Sí | Nombre de la taxonomía (ej. categories, tags) |
limit | integer | No | Máximo de elementos (1-100, predeterminado 50) |
cursor | string | No | Cursor de paginación |
Scope: content:read | Solo lectura: Sí
taxonomy_create_term
Crea un nuevo término en una taxonomía. Para taxonomías jerárquicas, especifica un parentId para crear un término hijo.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
taxonomy | string | Sí | Nombre de la taxonomía |
slug | string | Sí | Identificador seguro para URL |
label | string | Sí | Nombre para mostrar |
parentId | string | No | ID del término padre (para taxonomías jerárquicas) |
description | string | No | Descripción del término |
Scope: content:write
Herramientas de menú
menu_list
Lista todos los menús de navegación. Devuelve nombre, etiqueta y marcas de tiempo.
Sin parámetros.
Scope: content:read | Solo lectura: Sí
menu_get
Obtiene un menú por nombre incluyendo todos sus elementos en orden. Los elementos tienen una etiqueta, URL, tipo y padre opcional para anidamiento.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | Sí | Nombre del menú (ej. main, footer) |
Scope: content:read | Solo lectura: Sí
Herramientas de revisión
revision_list
Lista el historial de revisiones de un elemento de contenido, más reciente primero. Requiere que la colección soporte revisions.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
collection | string | Sí | Slug de la colección |
id | string | Sí | ID del elemento o slug |
limit | integer | No | Máximo de revisiones (1-50, predeterminado 20) |
Scope: content:read | Solo lectura: Sí
revision_restore
Restaura un elemento de contenido a una revisión anterior. Reemplaza el borrador actual con los datos de la revisión especificada. No se publica automáticamente — usa content_publish después si es necesario.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
revisionId | string | Sí | ID de la revisión a restaurar |
Scope: content:write
Descubrimiento OAuth
Los clientes MCP que soportan OAuth 2.1 pueden descubrir automáticamente cómo autenticarse. El servidor publica dos documentos de metadatos:
Metadatos de recurso protegido
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"]
}
Metadatos del servidor de autorización
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"
}
Cuando una solicitud no autenticada llega al endpoint MCP, el servidor devuelve:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"
Esto activa el flujo estándar de descubrimiento del cliente MCP.
Manejo de errores
Los errores de herramientas se devuelven como contenido de texto con isError: true:
{
"content": [{ "type": "text", "text": "Collection 'nonexistent' not found" }],
"isError": true
}
Los errores de scope y permisos lanzan errores de protocolo MCP:
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Insufficient scope: requires content:write"
},
"id": 1
}
Los errores a nivel de transporte (configuración errónea del servidor, excepciones no manejadas) devuelven el código de error JSON-RPC -32603 (Internal error) sin revelar detalles de implementación.