Crafty CMS (MCP + SDK de JavaScript)

Prompt de MCP: build-site. Lleva un sitio desde una descripción del negocio hasta un sitio web publicado: modelo de contenido, páginas, frontend con el SDK, vista previa y despliegue, confirmando el plan contigo antes de escribir. Argumentos: description (el negocio y lo que necesita el sitio) y, opcionalmente, site_id (un sitio existente). Necesita un token con crafty:write.

En Claude Code, escribe / y elige mcp__<server>__build-site, donde <server> es el nombre que le diste al servidor de Crafty; otros clientes lo muestran en su menú de prompts. Si tu cliente no muestra los prompts de MCP, pídele al agente que llame a get_guide getting-started y que lo siga.

¿Vas a rediseñar el sitio existente de un cliente? El prompt redesign-site lee su sitio actual y sus redes sociales, y propone el nuevo antes de construirlo (consulta get_guide migrate).

Crafty es un CMS headless y multi-tenant. Hay dos superficies de integración, que se usan en etapas distintas:

Superficie Propósito Autenticación Úsala para
Servidor MCP Crear y configurar contenido Token Bearer (PAT u OAuth), con alcances Crear sitios, páginas, bloques, colecciones, entradas, formularios; subir medios; publicar
@craftware/crafty-sdk Leer contenido publicado en una app Token de entorno X-API-Key Obtener páginas/bloques, entradas, colecciones, medios; enviar formularios desde un sitio

El flujo normal es configurar con el servidor MCP → construir un frontend con el SDK. Para un sitio nuevo, npm create crafty hace ambas cosas en un solo paso (consulta Iniciar un sitio nuevo).

Cuándo usarlo

  • Iniciar un sitio o proyecto de frontend de Crafty desde cero (npm create crafty, crafty create, plantillas oficiales).
  • Conectar Claude, Cursor, VS Code, Codex o ChatGPT al servidor MCP de un tenant de Crafty.
  • Usar las herramientas MCP de Crafty (list_sites, create_page, create_entry, create_form, publish_page, upload_media, …).
  • Instalar o importar @craftware/crafty-sdk / createCraftyClient.
  • Obtener páginas, entradas, colecciones o medios de Crafty en el código de una app, o enviar un formulario de Crafty.

Iniciar un sitio nuevo

Cuando el usuario quiere un sitio nuevo (no Crafty añadido a un frontend existente), no modeles el contenido ni escribas el frontend a mano: crea la base a partir de una plantilla oficial.

npm create crafty@latest mi-sitio -- --url=https://<tenant>.crafty.do
# mismo flujo: npx @craftware/crafty-cli create mi-sitio --url=https://<tenant>.crafty.do

Pregunta el framework y la plantilla (hoy: Astro, astro-services = "sitio de servicios", astro-store = "tienda", astro-booking = "barbería / salón (citas)" o astro-docs = "documentación"; Next.js y Nuxt están en camino), inicia sesión desde el navegador (flujo de dispositivo; el usuario aprueba, y se necesita una cuenta de administrador), crea el sitio con los tipos de bloque, colecciones, formulario y contenido de demostración de la plantilla, configura las rutas del editor visual y escribe .env (CRAFTY_API_URL, CRAFTY_API_KEY, CRAFTY_SITE), crafty.json y src/crafty.types.ts. Después:

cd mi-sitio && npm install && npm run dev   # npm run deploy → Crafty Hosting
  • Opciones (después de --): --url, --framework=astro, --template=astro-services|astro-store|astro-booking|astro-docs, --name, --site=<slug>, --draft (guarda el contenido de demostración como borradores; por defecto se publica para que la compilación estática lo muestre), --yes (sin preguntas). Los agentes deben pasar --url y --yes para que nada se bloquee esperando una respuesta; la aprobación en el navegador sigue requiriendo al usuario.
  • El comando solo es interactivo para el inicio de sesión: indícale al usuario que abra la URL impresa y apruebe el código.
  • Cuando termine, lee el AGENTS.md del proyecto y sigue sus convenciones (un renderizador por tipo de bloque en src/render/blocks/, rutas en src/lib/routes.ts, npm run types después de cambiar el modelo de contenido) en lugar de los pasos genéricos de la Fase 2.
  • Si el slug del sitio ya existe, falla antes de crear nada; si el aprovisionamiento falla a medio camino, indica el sitio a medio crear: elimínalo desde el panel o vuelve a ejecutar con otro --site.

Alcance

  • Dentro del alcance: usar un tenant de Crafty desplegado mediante su servidor MCP y el SDK publicado.
  • Fuera del alcance: construir o modificar el backend de Crafty en sí, los componentes internos del SDK o el pipeline de publicación (eso es la skill publish-sdk).

Modelo de dominio

  • Sitio: recurso de nivel superior del tenant. Todo lo demás pertenece a un sitio.
  • Página: una página web dentro de un sitio; draft o published; se compone de Bloques ordenados.
  • Bloque: una unidad de contenido en una página; su BlockType define el esquema de datos. Los tipos de bloque se pueden crear y editar por MCP (create_block_type, update_block_type).
  • Colección: un esquema de contenido estructurado (por ejemplo, "Entradas de blog"); define los campos que deben tener las Entradas.
  • Entrada: un registro dentro de una colección (por ejemplo, una sola publicación). Pertenece a un sitio y a una colección; draft o published (las entradas nuevas empiezan como borradores; solo las entradas publicadas llegan al SDK).
  • Formulario: un formulario de contacto o de comentarios dentro de un sitio; draft o published. Los formularios publicados aceptan envíos públicos sin autenticación.
  • Envío de formulario: datos enviados por el visitante; solo lectura después de crearse.
  • Medios: archivos adjuntos a un sitio mediante Spatie Media Library.

Modela primero el contenido con tipos de bloque

Usa por defecto tipos de bloque + páginas para todo lo que haya en un sitio: hero, estadísticas, acerca de, servicios, sectores, socios, contacto, pie de página y cualquier lista de elementos dentro de una sección. Recurre a una colección solo cuando los tipos de bloque realmente no encajen (consulta las excepciones más abajo).

Motivo: los bloques de página se validan contra su esquema al guardarse, aparecen en el editor visual con campos adecuados (sin JSON en bruto), se renderizan en llms-full.txt y viajan con la página cuando se publica.

Cómo modelarlo:

  • Un tipo de bloque por sección (hero, stats, solutions, footer, …); el slug del tipo de bloque es lo que buscan page.block("hero") / page.blocksOf("solutions") en el SDK.

  • Listas de elementos → un campo repeater, no un campo json ni una colección: items: repeater { icon, title, text, tags: stringList }. Un solo nivel (un repeater no puede contener otro repeater); tipos de subcampo: string, text, longText, url, number, decimal, boolean, select, reference (incluido model: "Media" para una imagen por elemento), stringList. Límites opcionales de elementos min / max.

  • Campos url (en el nivel superior o dentro de un repeater) aceptan URLs http(s), mailto:, tel:, anclas # y rutas relativas al sitio que empiezan con / (mailto: admite un solo destinatario, tel: necesita al menos 3 dígitos, los hosts IDN se convierten a punycode al validarse); cualquier otra cosa (javascript:, //host, otros esquemas) se rechaza.

  • Lista de cadenas simples → stringList (["AI", "Cloud"]).

  • Números: number solo admite enteros; precios, calificaciones y medidas son decimal ({ key: "price", type: "decimal", min: 0, precision: 2 }). Ambos admiten min / max opcionales; precision es el máximo de decimales.

  • Opciones fijas → select: { key: "condition", type: "select", options: ["new", { value: "used", label: "Used" }] } guarda un solo valor; añade multiple: true para guardar un arreglo de valores distintos. Las opciones son cadenas o { value, label } y deben ser únicas.

  • Imágenes y archivos → referencias a medios, nunca campos url o string. Sube primero el archivo (consulta "Medios" más abajo) y luego guarda su id:

    • una imagen (logotipo, hero, avatar, og:image): { key: "image", type: "reference", model: "Media" }, valor 42;
    • varias imágenes (galería, carrusel, muro de logotipos): { key: "gallery", type: "multiReference", model: "Media" }, valor [42, 43, 44];
    • una imagen por elemento de repeater: un subcampo reference con model: "Media" (multiReference solo existe en el nivel superior).

    La API pública devuelve el id bajo la clave del campo y añade <key>_media con el archivo ({ id, url, file_name, alt, mime_type, size, is_public, image_transform_base }, una lista para multiReference), así que el frontend renderiza crafty.image(block.data.image_media, { width: 800, format: "auto" }) con alt={block.data.image_media?.alt ?? ""}. Los editores obtienen un selector de medios en el panel y el editor visual puede cambiar la imagen. Un campo de cadena photo_url no les da nada de eso y se rompe cuando el archivo se mueve.

  • Datos compartidos (dirección, teléfono, redes sociales, texto del pie de página usado en varios lugares): los tipos de bloque no tienen bloques globales. Ponlos en una página dedicada (por ejemplo, slug site-settings) con un bloque site-settings y léelos con crafty.pages.get("site-settings").

  • Idiomas: no dupliques páginas por idioma. Configura los locales del sitio (configure_locales, por ejemplo es por defecto más en), escribe el contenido del locale predeterminado como de costumbre y luego añade cada traducción con upsert_translation (entradas; páginas con sus bloques por id). El frontend pasa locale (createCraftyClient({ locale }) o por llamada) y los campos sin traducir recurren al predeterminado. Marca con translatable: false los campos que nunca deben cambiar según el idioma (SKU, códigos). meta.translations en get() lista los locales de un elemento, para hreflang. Cuando la traducción con IA está habilitada para el espacio de trabajo (desde Crafty central) y para el sitio (configure_translation con enabled: true), translate_content traduce una entrada o página (con sus bloques) a los locales del sitio sin sobrescribir lo que escribió una persona, y configure_translation también activa la retraducción después de cada cambio y un glosario de términos que no se traducen.

  • No existe un tipo de campo json en los tipos de bloque. Si te encuentras queriendo uno, modélalo como repeater o stringList.

  • Referencias a registros: { type: "reference", collection: "<slug de la colección>" } (una entrada) o multiReference (varias); la API expande las referencias a las entradas referenciadas.

Usa una colección solo cuando el contenido sea un conjunto grande de registros independientes que listas, filtras, ordenas o paginas mediante la API (entries.list), por ejemplo publicaciones de blog o un catálogo de productos, o cuando muchas páginas distintas referencian el mismo registro. Unas pocas tarjetas, miembros del equipo o preguntas frecuentes dentro de una página son un repeater, no una colección.

Los campos de colección usan los mismos tipos que los tipos de bloque, más json: string, text, longText, url, number (entero), decimal, boolean, datetime, select, json, reference, multiReference, stringList, repeater (mismas reglas de un solo nivel y tipos de subcampo que en los tipos de bloque; un subcampo reference de Media obtiene <subkey>_media en la API). No recurras a json para listas en una colección: un campo json es un área de texto JSON en bruto en el panel, sin campos de entrada, sin validación de su contenido y sin selector de medios. En su lugar:

  • fotos de producto, galería → multiReference con model: "Media" (una sola imagen de portada → reference con model: "Media");
  • etiquetas, categorías, productos relacionados, autores → multiReference (o reference) con collection: "<slug>" apuntando a una colección pequeña de esos registros; una lista corta y fija de etiquetas (tallas, colores) → select con multiple: true o un stringList;
  • un conjunto corto y fijo de atributos (precio, sku, color) → un campo escalar para cada uno (decimal para el precio, select para un conjunto fijo de valores);
  • una lista de elementos ricos por entrada (especificaciones, días de itinerario, preguntas frecuentes de un producto) → un campo repeater en la colección;
  • una lista de elementos ricos que solo aparece dentro de una página → un repeater de tipo de bloque, no una colección.

Usa json solo para datos de máquina que ningún editor mantiene (por ejemplo, un arreglo de coordenadas de un mapa importado de otro lugar), y díselo al usuario cuando lo hagas.

Fase 1: configurar el contenido mediante el servidor MCP

Conéctate una vez y luego usa las herramientas. Conexión mínima con un Token de Acceso Personal (se muestra Claude Code):

claude mcp add --transport http crafty https://<your-tenant-domain>/mcp \
  --header "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"

La URL del servidor es por tenant: https://<your-tenant-domain>/mcp. Los tokens llevan alcances: crafty:read, crafty:write, crafty:publish y crafty:submissions (lectura de envíos de formularios, que pueden contener datos personales), y se aplican por herramienta (una herramienta sin su alcance queda oculta y da error si se llama). Solicita los alcances que necesite el agente. Guía completa de conexión para cada cliente (PAT y OAuth), alcances y catálogo completo de herramientas: lee references/mcp-configuration.md.

Flujo canónico de creación de contenido (nombres exactos de herramientas):

  1. list_sites → elige un site_id (o create_site).
  2. list_block_types → conoce los tipos de bloque disponibles (lista los tipos de todos los sitios; usa get_block_type con site_id para el esquema completo de un sitio). ¿Falta uno? create_block_type (site_id, name, fields).
  3. create_page con site_id, title y, opcionalmente, blocks: [{ block_type_id, data }]. Los data de cada bloque se validan contra el esquema del tipo de bloque; las claves no declaradas se descartan y se informan en un arreglo warnings de la respuesta.
  4. publish_page.

Tipos de bloque: get_block_type (site_id + block_type_id o slug) → create_block_type → update_block_type (fields reemplaza TODOS los campos; eliminar o cambiar el tipo de un campo que los bloques ya usan requiere force: true, y los datos existentes de los bloques nunca se reescriben) → delete_block_type (confirm: true, se rechaza mientras haya bloques que lo usen).

Colecciones/entradas: list_collections → create_entry (collection_slug, site_id, slug, data, y opcionalmente status: "draft" para prepararla sin publicar) → update_entry (guarda una revisión) → publish_entry / unpublish_entry. Para publicar o despublicar una página o entrada en un momento dado, usa schedule_publication (publish_at / unpublish_at). Formularios: create_form (site_id, name, slug, fields) → publish_form → list_form_submissions. Medios: sube los archivos antes de llenar el contenido y pon el id de medios devuelto en campos reference / multiReference con model: "Media" (list_media lista los ids existentes). upload_media (url, site_id, opcionalmente collection_name, sha256) acepta una URL http(s) o un pequeño URI de datos base64 (256 KB decodificados). Para un archivo binario local llama a request_media_upload (site_id, file_name, sha256, opcionalmente collection_name) y ejecuta el comando curl -T devuelto desde tu terminal; los bytes nunca pasan por el modelo y la subida se verifica contra el sha256. Los archivos solo se guardan en un disco público si el repositorio collection_name es público; créalo o cámbialo con manage_media_collection (site_id, key, is_public) ANTES de subir. Define siempre texto alternativo en las imágenes de contenido: pasa alt (una descripción concisa de lo que muestra la imagen, en el idioma del sitio, máximo 500 caracteres) a upload_media / request_media_upload, o define después con update_media (site_id, media_id, alt); list_media muestra alt: null para los archivos que aún no lo tienen. Las imágenes puramente decorativas pueden dejar alt vacío. Caché: set_api_cache_status (site_id, enabled) para activar o desactivar por sitio; flush_api_cache (site_id, resource opcional) para invalidar respuestas en caché después de ediciones masivas. Editor visual: configure_visual_editor (site_id, opcionalmente environment, frontend_url, routes, render_mode, upload_media_collection); consulta references/visual-editor.md.

update_page reemplaza todos los bloques cuando se envía blocks (envía de nuevo cada bloque con su id para conservarlo). update_entry crea una revisión. create_page siempre empieza como draft.

¿Migras un sitio existente (WordPress, Webflow, un sitio estático…)? No lo recrees a mano: sigue references/migrate-to-crafty.md. Usa bulk_upsert_entries (100 entradas por llamada, borradores, dry_run), import_media_from_urls (50 URL por llamada, sin duplicados) y upsert_redirects para que las URL antiguas sigan funcionando.

Fase 2: construir un frontend con el SDK

¿Vas a iniciar un sitio nuevo desde cero? Usa npm create crafty (Iniciar un sitio nuevo) y sigue el AGENTS.md de la plantilla en lugar de los pasos siguientes. Para añadir Crafty a un frontend existente:

npm install @craftware/crafty-sdk

Justo después de una versión, una caché de npm desactualizada puede fallar con ETARGET; reintenta con --prefer-online.

import { createCraftyClient } from "@craftware/crafty-sdk";

const crafty = createCraftyClient({
  baseUrl: "https://<your-tenant>.crafty.do", // el SDK añade /api/v1
  apiKey: process.env.CRAFTY_API_KEY,         // token de API del entorno (se envía como X-API-Key)
  siteSlug: "main-site",                      // necesario SOLO para forms.submit()
});

// las páginas se limitan al sitio al que resuelve el apiKey, sin argumento siteSlug
const page = await crafty.pages.get("home");
const hero = page.block("hero");          // primer bloque de ese tipo, o null
const posts = await crafty.entries.list("posts", { per_page: 15 });

Genera los tipos antes de escribir código contra el contenido. La forma de los campos depende de las colecciones y tipos de bloque del sitio; no los adivines. Ejecuta npx @craftware/crafty-cli types (lee CRAFTY_API_URL + CRAFTY_API_KEY de .env, o pasa --url / --api-key; --out cambia el archivo) para escribir crafty.types.ts con <Slug>Entry por colección, <Slug>BlockData / <Slug>Block por tipo de bloque y la unión CraftyPageBlock; convierte los resultados del SDK a esos tipos (page.blocks as unknown as CraftyPageBlock[]). Los tipos siguen el entorno de la clave de API (staging puede diferir de producción); vuelve a ejecutarlo después de create_block_type / update_block_type / update_collection. El JSON Schema en bruto está en GET /api/v1/schema. Detalles: references/sdk-development.md.

Imágenes: renderiza cada imagen de Crafty con crafty.image(media, { width, format: "auto" }) y crafty.srcset(media, [400, 800, 1200], { format: "auto" }) en lugar de la url en bruto; devuelven URL redimensionadas en AVIF/WebP cuando el servidor tiene las transformaciones de imagen activadas y recurren a la url original en caso contrario. Nunca construyas a mano URLs /cdn-cgi/image/.

pages.get() devuelve la página enriquecida con ayudas de bloques (page.block(type), page.blocksOf(type), page.byType, …). Configuración completa del cliente, cada recurso y forma de respuesta, ayudas de bloques, errores y un ejemplo de extremo a extremo: lee references/sdk-development.md.

Sirviendo llms.txt / llms-full.txt para agentes de IA y rastreadores: crafty.llms.handler() como ruta (Next/Astro/Nuxt), o crafty.llms.index() / full() escritos en dist/ para una compilación estática. El título, el resumen, el idioma y las colecciones sin ruta se configuran en la pantalla de administración "llms.txt" o con la herramienta MCP configure_llms. API completa, conexión por framework y manejo de errores: references/sdk-development.md.

Analítica y píxeles de anuncios (Google Tag Manager, GA4, Meta Pixel, TikTok Pixel): no pongas sus fragmentos directamente en el sitio. Define los ID en la pantalla de administración "Analítica" o con la herramienta MCP configure_analytics; Crafty Hosting inserta los cargadores oficiales en cada página HTML en el siguiente despliegue. Si el sitio ya carga GA4 o píxeles mediante GTM, define solo el contenedor de GTM. Si está alojado en otro lugar: renderiza (await crafty.analytics.get()).head antes de </head> en SSR, o llama a crafty.analytics.load() una vez en el navegador (se omite sola si el despliegue ya los inyectó). Detalles: references/sdk-development.md.

Datos estructurados (JSON-LD de schema.org para resultados enriquecidos de Google y búsqueda con IA): no escribas JSON-LD a mano. Completa los datos del negocio (tipo, dirección, horarios, teléfono, perfiles de redes sociales) con la herramienta MCP configure_structured_data, asigna colecciones a Product / Article / BlogPosting / FAQPage / Event / Service con structured_data en create_collection / update_collection, y renderiza crafty.structuredData(page, entries) en <head>: la página de inicio lleva el JSON-LD del negocio y las entradas asignadas llevan el suyo. Para bloques de preguntas frecuentes, crafty.structuredData(crafty.faqPage(block.data.items)). Crafty Hosting añade el JSON-LD del negocio a la página de inicio al desplegar cuando el sitio no lo renderiza. Detalles: references/sdk-development.md.

Tienda en línea (cuando la pestaña "Tienda" del sitio está activada): los productos son entradas de productos; mantén el carrito con createCart() de @craftware/crafty-sdk/cart, muestra los totales desde crafty.orders.quote(cart.items()) y envía al visitante al checkout alojado de Crafty con crafty.checkout.start(cart.items()). Nunca calcules precios ni impuestos en el frontend. Detalles: references/online-store.md.

Reservas en línea (cuando la pestaña "Citas" del sitio está activada): los servicios y profesionales son entradas de servicios y profesionales; muestra los horarios libres desde crafty.bookings.availability({ service, professional }) en el navegador y envía al visitante al checkout alojado con crafty.checkout.start({ booking: { service, professional, start } }). Nunca calcules los horarios libres en el frontend. Detalles: references/online-bookings.md.

Fase 3: desplegar un sitio estático con la CLI

npm install -D @craftware/crafty-cli

Añade a .env (la CLI lo lee al desplegar, separado de las variables VITE_*):

CRAFTY_API_URL=https://<your-tenant>.crafty.do/api/v1

Añade crafty.json en la raíz del proyecto:

{ "site": "<site-slug>", "environment": "production", "buildDir": "dist" }

Luego compila y despliega:

npm run build   # genera dist/
crafty deploy   # sube dist/ al entorno configurado

crafty.json es seguro para versionar en git. .env (que contiene tu clave de API) debe estar en .gitignore; usa .env.example con valores de ejemplo.

Editor visual (clic para editar): pregunta siempre

Cada vez que integres Crafty en un frontend, pregúntale al usuario exactamente una vez:

¿Quieres dejar el sitio preparado para el editor visual de Crafty?

  • Sí → sigue references/visual-editor.md por completo: marca el contenido con crafty.attrs() (botones y enlaces con hrefField solo cuando haya un destino guardado; rutas JSON con el índice guardado), entrega el token de vista previa para el modo de renderizado del sitio, llama a enableVisualEditing() (pasa client para sitios ssr; los sitios React/Vue vuelven a obtener datos y se remontan en onRefresh; los sitios híbridos guardan en caché la última respuesta), permite el enmarcado (frame-ancestors), verifica que las imágenes sean públicas, confirma qué host sirve realmente el dominio antes de definir frontend_url, ejecuta configure_visual_editor y verifica (incluidos los errores de hidratación).
  • No → no añadas nada y dile al usuario que puede activarse más adelante pidiendo «habilita el editor visual de Crafty».

Una solicitud posterior como «habilita el editor visual de Crafty» en un sitio existente ejecuta el flujo de Sí sin volver a preguntar.

Distinción clave: autenticación MCP vs. clave de API del SDK

Son dos credenciales distintas; no las mezcles:

  • MCP usa un token Bearer (Token de Acceso Personal o token de acceso OAuth) con alcances crafty:*. Lo usa un agente o editor para crear contenido.
  • SDK usa un token de API del entorno enviado en el encabezado X-API-Key. Resuelve el sitio y el entorno del lado del servidor, por lo que pages.* no necesita siteSlug. Lo usa el código de la app para leer contenido publicado.

Errores comunes

  • Host de tenant incorrecto → el endpoint MCP no resuelve. Usa exactamente https://<tenant-domain>/mcp; cada tenant tiene su propio endpoint.
  • 401 desde el servidor MCP → el token Bearer falta, no es válido o expiró; vuelve a conectar o genera otro.
  • Una herramienta falta o falla con "Insufficient OAuth scope" → el token no tiene el alcance de esa herramienta; emite de nuevo el token con crafty:write / crafty:publish / crafty:submissions según se necesite.
  • forms.submit() lanza BAD_REQUEST → no se definió siteSlug en createCraftyClient(). Los envíos van al endpoint público; la clave de API, cuando se define, solo elige el entorno donde cae el envío.
  • update_entry / update_page borran los campos que no envías → ambas herramientas MCP hacen un reemplazo completo, no una mezcla. Si envías solo los campos nuevos, todos los demás se eliminan en esa revisión. Obtén siempre primero la entrada actual (get_entry), combina tus cambios en el mapa completo de campos y luego llama a update_entry con el objeto completo combinado. Los data de la entrada se validan contra el esquema de la colección: los valores inválidos se rechazan y las claves no declaradas se descartan (se listan en warnings).
  • create_page / update_page rechazan los datos de bloque → los datos de bloque se validan contra el tipo de bloque (blocks.2.data.items.1.title: …), los campos number son enteros (usa decimal para precios), los valores de select deben ser una de las opciones declaradas, los campos string admiten máximo 255 caracteres y block_type_id debe pertenecer al mismo sitio. Lee primero el esquema con get_block_type.
  • Faltan datos en un bloque sin aviso → las claves no declaradas en el tipo de bloque se descartan; revisa el arreglo warnings de la respuesta.
  • Modelaste una lista como colección o como campo json → usa un tipo de bloque con un repeater (o stringList) en su lugar; consulta "Modela primero el contenido con tipos de bloque". Dentro de una colección, usa repeater, stringList, select con multiple: true o multiReference (a medios o a otra colección) en lugar de json.
  • Adivinaste un nombre o forma de campo (block.data.title vs heading, un valor de select, image vs image_media.url) → genera crafty.types.ts con crafty types y deja que el compilador lo detecte.
  • Guardaste una imagen como campo url / string (photo_url, image, logo) → súbela a la biblioteca de medios y usa un reference (multiReference para varias) con model: "Media"; renderiza crafty.image(<key>_media, { width, format: "auto" }). Cambia un campo existente con update_block_type / update_collection y vuelve a guardar el contenido con los ids de medios.
  • Servir la imagen original (<img src={media.url}>) → las subidas grandes perjudican el LCP. Usa crafty.image(media, { width, format: "auto" }) más crafty.srcset(); recurre a url cuando las transformaciones no están disponibles.
  • Imágenes subidas sin texto alternativo → define alt con update_media para cada imagen de contenido (list_media muestra cuáles tienen alt: null) y renderiza <key>_media.alt en el <img alt> del frontend.
  • El contenido no es visible en el SDK → no está publicado. Las páginas y formularios deben publicarse (publish_page / publish_form); las entradas creadas como borradores necesitan publish_entry.
  • Confundir los dos tokens → la apiKey del SDK (X-API-Key) no es un token Bearer de MCP.
  • Copia desactualizada de la skill → una copia de este skill en el proyecto (por ejemplo, .claude/skills/crafty-cms) no se actualiza con el SDK. Después de actualizar @craftware/crafty-sdk, copia node_modules/@craftware/crafty-sdk/skills/crafty-cms encima.

Referencias

  • references/mcp-configuration.md: conexión de cada cliente, alcances y el catálogo completo de herramientas MCP con flujos de trabajo.
  • references/sdk-development.md: la superficie completa del SDK, ayudas de bloques, manejo de errores y ejemplos.
  • references/online-store.md: la tienda en línea: cómo activarla (configure_store), la colección de productos, carrito, cotizaciones, checkout alojado, pedidos y webhooks.
  • references/online-bookings.md: reservas en línea: cómo activarlas (configure_bookings), servicios y profesionales, horarios y bloqueos, horarios libres, checkout alojado, calendario, API, herramientas MCP y webhooks.
  • references/migrate-to-crafty.md: migración de un sitio existente (WordPress u otro) a Crafty: rastreo, clasificación, modelado, importación masiva como borradores, medios, redirecciones, revisión humana y publicación.
  • references/visual-editor.md: preparación de un sitio para el editor visual: la pregunta obligatoria, marcado, tokens de vista previa, puente, encabezados, configure_visual_editor y verificación.