Configurar Crafty mediante el servidor MCP
Usa esto cuando un agente deba crear y configurar contenido en un tenant de Crafty: sitios, páginas/bloques, colecciones/entradas, formularios, medios y publicación.
URL del servidor
El endpoint MCP es por tenant y se encuentra en la ruta /mcp del propio dominio del tenant:
https://<your-tenant-domain>/mcp
El tenant se identifica por el host de la solicitud, así que cada tenant tiene su propio endpoint. Si usas un host incorrecto, no se resolverá el tenant. Cada solicitud debe estar autenticada (token Bearer).
Autenticación
Hay dos familias, ambas con un token Bearer en cada solicitud:
Personal Access Token (PAT): la opción más sencilla
Genera un Personal Access Token de larga duración desde tu perfil de Crafty, eligiendo los scopes que necesites, y luego pégalo en la configuración del editor como cabecera Authorization: Bearer …. Es ideal para CLIs y configuraciones locales. Si deja de funcionar, regénéralo y actualiza la configuración.
OAuth 2.0: mediante un cliente MCP
Crea un "cliente MCP" en tu perfil de Crafty y elige un tipo de concesión (grant type):
- Authorization Code: flujo estándar con redirección del navegador para herramientas que abren un navegador (Claude.ai, Claude Code, Codex, Cursor, VS Code, ChatGPT). Selecciona la URI de redirección que corresponda a tu herramienta. La mayoría se registra sola (ver más abajo), así que un cliente manual solo es necesario cuando la herramienta pide un
client_id. - Device Code (RFC 8628): para clientes que no pueden abrir un navegador y admiten esta concesión; el cliente muestra un código que apruebas en el navegador.
Al crear el cliente manualmente en tu perfil se te muestran el client_id y un client_secret de un solo uso. Cópialo de inmediato (los PAT también muestran el token una sola vez). Los clientes que se registran automáticamente mediante discovery (por ejemplo, Claude.ai, Claude Code, Codex, Cursor, ChatGPT) se registran como clientes públicos y no reciben client secret.
Los clientes OAuth que admiten el autodescubrimiento de MCP (Claude.ai, Claude Code, Codex, Cursor, ChatGPT) pueden encontrar todo solo con la URL del servidor: el discovery se publica en /.well-known/oauth-protected-resource y /.well-known/oauth-authorization-server, con authorization_endpoint /oauth/authorize, token_endpoint /oauth/token, registration_endpoint dinámico /oauth/register, PKCE (S256) y las concesiones authorization_code + refresh_token. Las URI de redirección registradas deben ser https, un esquema de aplicación personalizado o http en una dirección de loopback (así funcionan los callbacks de CLI locales). Estos endpoints OAuth están en la raíz del tenant, no bajo /mcp.
Los clientes que implementan la especificación de autorización MCP 2026-07-28 pueden omitir el registro y usar un Client ID Metadata Document (CIMD): el client_id es una URL https que sirve los metadatos del cliente en JSON (client_id igual a esa URL, redirect_uris, y client_name opcional). Crafty anuncia client_id_metadata_document_supported: true, obtiene el documento en /oauth/authorize (solo direcciones públicas, sin redirecciones, 5 KB como máximo, en caché según el max-age de su Cache-Control) y exige que la redirect_uri esté listada en él. Solo se aceptan de esta forma los clientes públicos (token_endpoint_auth_method: "none"). El registro dinámico sigue funcionando para los clientes que aún lo usan.
Scopes
Al crear un token (PAT o cliente OAuth) concedes uno o más scopes. Los scopes se aplican por herramienta: una herramienta solo se anuncia si tu token tiene el scope requerido, y si la invocas sin ese scope falla con un error de MCP (Insufficient OAuth scope). Concede los scopes que el agente necesite:
| Scope | Desbloquea | Herramientas |
|---|---|---|
crafty:read |
Leer sitios, páginas, tipos de bloque, colecciones, entradas, medios y definiciones de formularios | todas las list_* / get_* excepto los envíos de formularios |
crafty:write |
Crear, actualizar y eliminar sitios, páginas, colecciones, entradas, medios y formularios; configurar el editor visual, llms.txt y la caché de la API |
todas las create_* / update_* / delete_*, upload_media, configure_visual_editor, configure_llms, flush_api_cache, set_api_cache_status |
crafty:publish |
Publicar, despublicar y programar páginas, entradas y formularios | publish_page, unpublish_page, publish_entry, unpublish_entry, schedule_publication, publish_form, unpublish_form |
crafty:submissions |
Leer los envíos de formularios (pueden contener datos personales) | list_form_submissions, get_form_submission |
Solicita los cuatro para tener acceso completo a la creación de contenido y a los envíos. Los scopes también se anuncian en el discovery de OAuth; el endpoint /mcp además exige un token autenticado válido en cada solicitud.
Conectar cada cliente
Reemplaza https://<tenant>/mcp por la URL de tu tenant y YOUR_PAT por un Personal Access Token.
Claude.ai (Authorization Code): ve a Customize → Connectors → + → Add custom connector, introduce la URL del servidor, haz clic en Connect y aprueba en el navegador (en planes Team/Enterprise, un Owner la añade primero en Organization settings → Connectors). Se registra automáticamente; si prefieres usar tu propio cliente, crea un cliente MCP Authorization Code con la URI de redirección https://claude.ai/api/mcp/auth_callback y pega su client_id/client_secret en Advanced settings del conector.
Claude Code (CLI):
# Opción A: PAT
claude mcp add --transport http crafty https://<tenant>/mcp \
--header "Authorization: Bearer YOUR_PAT"
# Opción B: OAuth en el navegador (se registra automáticamente)
claude mcp add --transport http crafty https://<tenant>/mcp
# después, dentro de Claude Code: /mcp → crafty → Authenticate
Cursor (Authorization Code): añade lo siguiente a ~/.cursor/mcp.json y autoriza cuando se te pida (se registra automáticamente; para tu propio cliente usa la URI de redirección cursor://anysphere.cursor-deeplink/mcp/auth_callback):
{ "mcpServers": { "crafty": { "url": "https://<tenant>/mcp" } } }
VS Code (GitHub Copilot, PAT): añade lo siguiente a .vscode/mcp.json, luego abre Copilot Chat → modo Agent y selecciona las herramientas de Crafty. (Alternativa OAuth: cliente Authorization Code con la URI de redirección vscode://github.copilot-chat/mcp/auth_callback).
{ "servers": { "crafty": { "type": "http", "url": "https://<tenant>/mcp",
"headers": { "Authorization": "Bearer YOUR_PAT" } } } }
Codex CLI (escribe en ~/.codex/config.toml):
# Opción A: PAT, leído desde una variable de entorno
export CRAFTY_MCP_TOKEN="YOUR_PAT"
codex mcp add crafty --url https://<tenant>/mcp --bearer-token-env-var CRAFTY_MCP_TOKEN
# Opción B: OAuth en el navegador (se registra automáticamente)
codex mcp add crafty --url https://<tenant>/mcp
codex mcp login crafty
ChatGPT (Authorization Code; Plus, Pro, Business, Enterprise, Education, web): activa Developer mode en Settings → Apps & Connectors → Advanced settings, haz clic en Create, pega la URL del servidor, elige OAuth y aprueba en el inicio de sesión de Crafty. Se registra automáticamente.
Catálogo de herramientas
Todas las herramientas devuelven una respuesta de texto JSON. Los IDs son enteros; los slugs son cadenas. La paginación es de 25 por página por defecto (per_page con máximo de 100 cuando se admite).
Lectura (crafty:read)
| Herramienta | Obligatorio | Opcional | Devuelve |
|---|---|---|---|
list_sites |
— | — | todos los sitios (id, name, slug, domain, is_active) |
get_site |
site_id |
— | un sitio |
list_pages |
site_id |
status (draft/published) |
páginas paginadas, ordenadas por título |
get_page |
page_id |
— | página con sus bloques (incl. tipos de bloque) |
list_block_types |
— | — | tipos de bloque y sus esquemas (no se filtran por sitio) |
get_block_type |
site_id, y block_type_id o slug |
— | un tipo de bloque del sitio con su esquema completo (field_definitions) |
list_collections |
— | site_id |
colecciones activas (con su id, usado por update_collection / delete_collection) |
list_entries |
collection_slug |
site_id, status, per_page |
entradas paginadas (primero el id más reciente), cada una con status y published_at |
get_entry |
entry_id |
— | una entrada con su colección |
list_media |
site_id |
collection, per_page |
medios paginados del sitio; cada elemento tiene alt (null si falta) |
list_forms |
— | site_id, status, per_page |
formularios paginados, ordenados por nombre |
get_form |
form_id |
— | un formulario con sus definiciones de campos |
list_redirects |
site_id |
environment (por defecto production) |
las redirecciones del entorno { source, destination, status_code } |
get_assistant_settings |
site_id |
environment (por defecto production) |
la configuración del asistente del sitio, status (mode assistant/whatsapp/off, blocked_reason server/plan/disabled/quota, el usage de este mes), answers_count y las options permitidas |
list_assistant_answers |
site_id |
environment (por defecto production) |
las respuestas fijas privadas del asistente { id, question, answer, author, updated_at }, de la más reciente a la más antigua |
list_webhooks |
site_id |
environment (por defecto production) |
webhooks de contenido (solo administradores) con sus eventos y últimas entregas; upsert_webhook / delete_webhook los gestionan (crafty:write) |
Envíos (crafty:submissions)
Los envíos de formularios pueden contener datos personales, por eso requieren su propio scope.
| Herramienta | Obligatorio | Opcional | Devuelve |
|---|---|---|---|
list_form_submissions |
form_slug |
site_id, per_page |
envíos paginados, del más reciente al más antiguo |
get_form_submission |
form_slug, submission_id |
site_id |
un envío con sus datos |
Escritura (crafty:write)
| Herramienta | Obligatorio | Opcional | Notas |
|---|---|---|---|
flush_api_cache |
site_id |
resource |
vacía las respuestas de API públicas en caché del sitio; resource = pages, entries, collections, media o all (por defecto all) |
set_api_cache_status |
site_id, enabled |
— | activa (true) o desactiva (false) la caché de respuestas de la API pública del sitio; es una anulación por sitio que conserva el resto de la configuración de caché (TTL, reglas de bypass) |
create_site |
name |
slug |
el slug se genera automáticamente a partir del nombre; devuelve el sitio (con un api_token generado) |
create_block_type |
site_id, name, fields |
slug, description |
fields: [{ key, type, label?, required?, model?, collection? }]; tipos string, text, longText, url, number (entero), decimal (cualquier número; min, max, precision opcionales = máximo de decimales), boolean, datetime, select (options: ["a", { value: "b", label: "B" }], multiple?: true para un array de valores), reference, multiReference, stringList, repeater. number también admite min/max opcionales. Un repeater recibe fields: [...] (subtipos string, text, longText, url, number, decimal, boolean, select, reference, stringList; sin repeater anidado) y min/max opcionales de número de elementos. Los campos url aceptan URLs http(s), mailto:, tel:, #anclas y rutas relativas al sitio que empiecen por / (mailto: admite un único destinatario, tel: necesita al menos 3 dígitos, los hosts IDN se convierten a punycode al validarse). Imágenes y archivos: reference / multiReference con model: "Media" (valor: id(s) de medios); entradas de una colección: collection: "<slug>". El slug se genera a partir del nombre y es único por sitio |
update_block_type |
site_id, block_type_id |
name, slug, description, fields, force |
fields reemplaza todos los campos. Eliminar o cambiar el tipo de un campo (o subcampo de repeater) que los bloques existentes llenan se rechaza salvo que uses force: true; los datos de los bloques nunca se reescriben |
delete_block_type |
site_id, block_type_id, confirm: true |
— | se rechaza mientras haya bloques que usen el tipo |
create_page |
site_id, title |
slug, blocks |
empieza como draft; blocks: [{ block_type_id, data }] se crean en orden; data se valida contra el tipo de bloque (errores como blocks.2.data.items.1.title), las claves no declaradas se descartan y se listan en warnings |
update_page |
page_id |
title, slug, blocks |
si se envía blocks, se reemplazan TODOS los bloques existentes (envía cada bloque con su id para conservarlo); misma validación y warnings que create_page |
create_collection |
site_id, name |
slug, description, fields |
slug generado automáticamente a partir del nombre; fields: [{ key, type, label, required?, model?, repository?, collection?, options?, multiple?, min?, max?, precision?, fields? }]; tipos string, text, longText, url, number (entero), decimal, boolean, datetime, select, json, reference, multiReference, stringList, repeater; decimal, select y repeater aceptan las mismas opciones que en create_block_type; las configuraciones no válidas (por ejemplo, un select sin opciones o con valores duplicados) se rechazan. Imágenes: { key: "cover", type: "reference", model: "Media", repository: "library" }; galerías: multiReference; evita json para contenido que mantengan los editores |
update_collection |
collection_id |
name, description, is_active, fields |
fields es una lista de reemplazo completo en el orden de visualización; cada campo { key, type, label?, required?, model?, repository?, collection?, relation?, slug_source?, title_source?, options?, multiple?, min?, max?, precision?, fields? } (referencia a medios: { key: "image", type: "reference", model: "Media", repository: "library" }) |
delete_collection |
collection_id, confirm: true |
— | elimina de forma permanente la colección y todas sus entradas |
create_entry |
collection_slug, site_id, slug |
data, status |
status = draft (por defecto, oculta en la API pública hasta publish_entry) o published; data = pares clave/valor de campos, validados contra el esquema de la colección (number entero, decimal dentro de min/max/precision, select uno de sus valores de opción o un array de valores distintos cuando multiple, elementos de repeater según sus subcampos; los datos no válidos se rechazan como data.<field>: <message>; las claves no declaradas se descartan y se listan en warnings) |
update_entry |
entry_id |
data, slug |
guarda los datos anteriores en una nueva revisión; data se valida como en create_entry (los campos obligatorios no se exigen, solo se comprueban los enviados) y los datos rechazados no crean revisión |
bulk_upsert_entries |
site_id, collection_slug, entries |
dry_run, publish |
hasta 100 { slug, external_id?, data } por llamada; busca primero una entrada existente por external_id y luego por slug, y si no existe la crea; data se valida como en create_entry y reemplaza los datos guardados al actualizar (con una revisión); las entradas nuevas son borradores salvo que publish: true (requiere crafty:publish); los elementos fallan de forma individual; dry_run informa created / updated / failed por elemento sin guardar |
upload_media |
url, site_id |
collection_name (por defecto library), file_name, sha256, alt |
alt = texto alternativo para imágenes (máximo 500 caracteres); URL http(s) o URI de datos base64 (máximo 256 KB decodificados); sha256 rechaza y descarta la subida si no coincide; la respuesta devuelve el sha256 almacenado, visibility y un warning cuando el archivo es privado |
import_media_from_urls |
site_id, urls |
collection_name (por defecto library) |
hasta 50 URL http(s) públicas por llamada, con las mismas comprobaciones de SSRF, tamaño y tipo que upload_media; una URL ya importada al sitio (o repetida en el lote) no se vuelve a descargar; por URL: status imported / existing / duplicate / failed, media_id, original_url |
request_media_upload |
site_id, file_name, sha256 |
collection_name (por defecto library), size, alt |
alt se guarda con el archivo una vez subido; devuelve una URL firmada de un solo uso válida durante 15 minutos y un comando curl -T listo para subir un archivo binario local sin enviar sus bytes a través del modelo; máximo 25 MB; se verifica contra sha256 |
manage_media_collection |
site_id, key |
name, description, is_public |
crea o actualiza un repositorio de medios; is_public: true da URL públicas permanentes (los archivos existentes se mueven en segundo plano); los repositorios nuevos son privados |
update_media |
site_id, media_id, alt |
— | establece el texto alternativo (máximo 500 caracteres) de un archivo multimedia; null o "" lo borra |
delete_media |
site_id, media_id, confirm: true |
— | elimina de forma permanente un archivo multimedia (ids de list_media) |
create_form |
site_id, name, slug |
description, fields, captcha |
fields: [{ key, type, label, required? }]; empieza como draft; protegido con captcha por defecto cuando Crafty tiene uno configurado (captcha: false para desactivarlo), y el frontend debe mostrarlo con crafty.forms.captcha() |
update_form |
form_id |
name, slug, description, fields, notification_emails |
solo cambian los campos enviados; fields/notification_emails se reemplazan |
configure_visual_editor |
site_id |
environment (por defecto production), frontend_url, routes (page_pattern, home_slug, collections, fixed), render_mode (ssr/hybrid/static/unknown), upload_media_collection |
configuración del editor visual de un entorno; solo cambian las claves enviadas, null restablece el valor por defecto; solo con site_id únicamente lee; devuelve la configuración efectiva más llms_warnings (las advertencias del generador de llms.txt, ya que las rutas también le afectan) |
configure_llms |
site_id |
environment (por defecto production), title, summary, language, excluded_collections (colecciones con ruta que se excluyen), included_collections (colecciones sin ruta que se incluyen), collection_languages |
solo administradores; misma semántica de lectura y actualización parcial que configure_visual_editor; solo cambian las claves enviadas, null restablece el valor por defecto; solo con site_id únicamente lee; devuelve la configuración efectiva, las advertencias de configuración y las primeras 40 líneas del llms.txt generado |
| configure_assistant | site_id y al menos un ajuste | environment (por defecto production), enabled, whatsapp_enabled, whatsapp_number, whatsapp_label, contact_form, hours (weekdays/saturday/sunday → { open, close } o null), timezone, greeting, suggestions (máximo 3), tone (friendly/formal/technical), guidelines, privacy_url, appearance (name, color, font, corners, position, avatar_on_launcher) | solo cambian las claves enviadas, null restablece el valor por defecto; el avatar se sube desde el panel; devuelve la configuración efectiva y el status |
| configure_store | site_id | environment (por defecto production), enabled, currency, order_prefix, prices_include_tax, taxes, default_tax, delivery (enabled, fee, free_from, tax, zone), pickup (enabled, address), payment_methods (cash_on_delivery, pay_at_pickup, transfer), transfer_accounts, notify_emails, customer_emails, return_url, color, logo_media_id, font, corners, checkout_fields, checkout_analytics, captcha | solo administradores; enabled: true requiere que la tienda esté en el plan y crea (o adopta) la colección productos y la URL de pago; solo cambian las claves enviadas; solo con site_id únicamente lee; devuelve la configuración, open, el resumen de productos y checkout_url. Consulta online-store.md |
| list_orders | site_id | environment, status, payment_status, search, limit, page | requiere el permiso orders (lectura) |
| get_order | site_id, number | environment | el pedido con sus líneas (id para update_order), fields, metadata, payment_url, next_statuses e historial |
| create_order | site_id, customer, fulfillment, payment_method, items | environment, address, fields, metadata, coupon, locale, expected_total | permiso de escritura orders; origen mcp; devuelve el pedido y su payment_url |
| update_order | site_id, number | environment, claves de edición (items, fulfillment, payment_method, customer, address, fields, metadata, coupon, coupon_below_minimum), notify, status, paid, payment_reference, internal_note | solo edita pedidos abiertos no pagados (los metadatos siempre); las líneas antiguas conservan su precio; dispara order.updated |
| list_coupons | site_id | environment, status, search | cupones con status, uses, orders, sales, discounted; requiere el permiso coupons (lectura) |
| get_coupon | site_id, code | environment | las reglas de un cupón (productos como slugs) y sus resultados |
| upsert_coupon | site_id, code | environment, name, type, value, max_discount, min_subtotal, applies_to, products, max_uses, max_uses_per_email, starts_at, ends_at, is_active | crea o modifica por código; un cupón ya usado conserva su código y tipo; requiere escritura en coupons |
| delete_coupon | site_id, code | environment | elimina un cupón no usado, pausa uno usado |
| configure_bookings | site_id | environment (por defecto production), enabled, timezone, hours (combinadas por día de la semana), closed_dates, min_notice_minutes, max_days_ahead, slot_interval, cancel_notice_minutes, auto_confirm, reminder_hours, payment_methods (pay_at_venue, transfer), address, phone, checkout_fields | solo administradores; enabled: true requiere reservas en el plan y crea (o adopta) las colecciones servicios y profesionales; solo cambian las claves enviadas; solo con site_id únicamente lee; devuelve la configuración, open, los slugs de servicios y profesionales y checkout_url. Consulta online-bookings.md |
| get_booking_availability | site_id | environment, service, professional, from, to | sin service: los servicios y profesionales reservables; con él: huecos libres por día local (hasta 31 días), cada uno con los profesionales libres |
| list_bookings | site_id | environment, from, to, status, professional, search, metadata, limit, page | requiere el permiso de reservas (lectura) |
| get_booking | site_id, number | environment | la reserva con next_statuses, internal_note, customer_url y events |
| create_booking | site_id, service, start, customer | environment, professional, payment_method, fields, metadata, outside_hours, internal_note, locale | permiso de escritura de reservas; origen mcp; un hueco ocupado responde SLOT_UNAVAILABLE |
| update_booking | site_id, number | environment, start, professional, outside_hours, status, paid, payment_reference, internal_note, metadata | mueve la reserva, cambia el estado o la marca como pagada; devuelve la reserva y applied |
| set_professional_schedule | site_id, professional | environment, mode (shop/own), hours | sin mode solo lee; los horarios propios siempre quedan dentro de los de la tienda |
| manage_booking_blocks | site_id, action (list/create/delete) | environment, professional, repeat (once/weekly), from_date, to_date, all_day, start_time, end_time, weekdays, reason, id (para eliminar) | bloquea tiempo para un profesional o para toda la tienda; create devuelve cuántas reservas ya caen dentro |
| upsert_assistant_answer | site_id, question, answer | environment (por defecto production), answer_id | añade una respuesta fija o edita la que tiene answer_id; el asistente la usa desde el siguiente mensaje del visitante |
| delete_assistant_answer | site_id, answer_id | environment (por defecto production) | elimina una respuesta fija |
| upsert_redirects | site_id, redirects | environment (por defecto production), dry_run | hasta 500 { source, destination, status_code? } por llamada, emparejadas por source; source es una ruta que empieza por / (sin espacios, ? ni #; se admiten los splats * y los :placeholders de Cloudflare Pages), destination una ruta o una URL http(s) absoluta, status_code 301 (por defecto) / 302 / 307 / 308; los elementos no válidos aparecen en failed y se omiten. Crafty hosting las escribe en _redirects en el siguiente despliegue (después de las reglas propias del sitio; límite de 2000 estáticas + 100 dinámicas); los frontends personalizados leen GET /api/v1/redirects |
| delete_redirect | site_id, source | environment (por defecto production) | elimina una redirección |
Las herramientas del asistente siguen la sección Asistente del panel: los administradores siempre, los editores con acceso de lectura pueden leer y los editores con acceso de escritura pueden modificar.
Las herramientas de redirección siguen el permiso Pages: leer requiere acceso a páginas, modificar requiere acceso de escritura.
Publicación (crafty:publish)
| Herramienta | Obligatorio | Efecto |
|---|---|---|
publish_page |
page_id |
estado → published, establece published_at |
unpublish_page |
page_id |
estado → draft, borra published_at |
publish_entry |
entry_id |
estado → published, establece published_at |
unpublish_entry |
entry_id |
estado → draft, borra published_at; la entrada desaparece de la API pública y de las referencias |
schedule_publication |
type (page/entry), id, y publish_at y/o unpublish_at |
define cuándo un borrador se publica solo y/o cuándo un elemento publicado vuelve a borrador; fechas ISO 8601 (sin desfase → la zona horaria regional del usuario), null borra una de ellas, unpublish_at debe ser posterior a publish_at; se aplica cada minuto |
publish_form |
form_id |
estado → published (ahora acepta envíos públicos) |
unpublish_form |
form_id |
estado → draft (deja de aceptar envíos) |
Flujos de trabajo canónicos
Modelar contenido con tipos de bloque (recomendado)
list_sites→ elige unsite_id(ocreate_site).- Para cada sección del diseño,
create_block_type(oget_block_typesi ya existe). Usa unrepeaterpara listas de elementos,stringListpara listas de cadenas simples yreference/multiReferenceconmodel: "Media"para imágenes (súbelas primero); usa una colección solo para conjuntos grandes e independientes de registros. create_pageen el idioma por defecto conblocks: [{ block_type_id, data }, …]; corrige los erroresblocks.N.data.…y revisawarnings. Para otros idiomas, ejecutaconfigure_localesuna vez y luegoupsert_translationpor página (bloques por id desdeget_page), otranslate_contentpara que la IA lo traduzca una vez que la traducción con IA esté habilitada para el workspace y el sitio (configure_translation).publish_page.- Para cambiar un esquema más adelante,
update_block_typecon la lista COMPLETA de campos (usaforce: truesolo si aceptas dejar huérfanos los datos que los bloques ya contienen).
Crear y publicar una página
list_sites→ elige unsite_id(ocreate_site).get_block_type/list_block_types→ anota cadaidy su esquema de datos.create_pageconsite_id,titleyblocks: [{ block_type_id, data }, …].publish_pagecon elpage_iddevuelto.
Contenido estructurado
list_collections(opcionalmente consite_id) → elige uncollection_slug, ocreate_collectioncon susfields(update_collectionlos cambia más adelante).create_entryconcollection_slug,site_id,slugydata.update_entrypara editar (cada edición guarda una revisión).
Formularios
create_formconsite_id,name,slugyfields.publish_formpara que acepte envíos públicos.list_form_submissions/get_form_submissionpara leer lo que enviaron los visitantes.
Medios
- ¿Necesitas URL públicas permanentes? Primero
manage_media_collectionconsite_id,key,is_public: true(si no, los archivos reciben URL firmadas con caducidad). - Desde una URL o un archivo pequeño:
upload_mediaconurl,site_idyalt(opcionalmentecollection_name,sha256). Desde un archivo local: calculashasum -a 256 <file>, llama arequest_media_upload(conalt) y luego ejecuta el comandocurl -Tdevuelto. list_mediapara confirmar y obtener URL; asigna conupdate_mediaun textoalta cada imagen de contenido que no lo tenga.
Migrar un sitio existente
Sigue migrate-to-crafty.md: rastrear → clasificar → modelar → import_media_from_urls → bulk_upsert_entries (borradores, primero dry_run) → upsert_redirects → revisión humana → publicar.
Editor visual
- Prepara el frontend (consulta
visual-editor.md). configure_visual_editorconsite_id,frontend_url,routesyrender_mode.- Pide al usuario que abra Editar sitio en el admin para confirmar que la edición al hacer clic funciona.
Gestión de la caché de respuestas de la API
La API pública tiene una caché de respuestas basada en etiquetas (habilitada por entorno). Las anulaciones por sitio tienen prioridad sobre la configuración global.
- Desactiva la caché de un sitio antes de las ediciones masivas y vuelve a activarla al terminar:
set_api_cache_statusconsite_idyenabled: false.- Haz todos tus cambios de contenido (
update_entry,update_page, etc.). set_api_cache_statusconenabled: true.flush_api_cacheconsite_id(y opcionalmenteresource) para borrar las entradas obsoletas.
- Vacía solo un tipo de recurso después de cambios puntuales:
flush_api_cacheconsite_idyresource: "entries"tras actualizar entradas.flush_api_cacheconsite_idyresource: "pages"tras actualizar páginas.
flush_api_cachesinresource(o conresource: "all") borra de una vez todas las respuestas en caché del sitio.
Solución de problemas
- 401 / error de autenticación: el token Bearer falta, no es válido o ha caducado; vuelve a conectar o regenéralo. Cada solicitud a
/mcpdebe llevar un token válido. - Falta una herramienta o falla con
Insufficient OAuth scope: el token no tiene el scope de esa herramienta. Las herramientas solo se anuncian si el token tiene el scope correspondiente (crafty:writepara crear/editar,crafty:publishpara publicar,crafty:submissionspara leer envíos de formularios). Vuelve a emitir el token con los scopes que necesitas. - Tenant no encontrado: host incorrecto; usa el dominio exacto del tenant. Los endpoints de token de OAuth están en la raíz del tenant (
/oauth/...), no bajo/mcp. - OAuth falla con un cliente creado manualmente: su tipo debe coincidir con el flujo (Authorization Code o Device Code), su URI de redirección debe ser exactamente la que usa la herramienta, y
client_id/client_secretdeben coincidir. Las herramientas que se registran automáticamente no necesitan cliente manual. - Token/secreto perdido:
client_secrety los PAT se muestran solo una vez al crearlos; regénéralos si los pierdes.