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 concrafty:write.En Claude Code, escribe
/y eligemcp__<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 aget_guide getting-startedy que lo siga.¿Vas a rediseñar el sitio existente de un cliente? El prompt
redesign-sitelee su sitio actual y sus redes sociales, y propone el nuevo antes de construirlo (consultaget_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--urly--yespara 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.mddel proyecto y sigue sus convenciones (un renderizador por tipo de bloque ensrc/render/blocks/, rutas ensrc/lib/routes.ts,npm run typesdespué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;
draftopublished; 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;
draftopublished(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;
draftopublished. 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, …); elslugdel tipo de bloque es lo que buscanpage.block("hero")/page.blocksOf("solutions")en el SDK.Listas de elementos → un campo
repeater, no un campojsonni 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(incluidomodel: "Media"para una imagen por elemento),stringList. Límites opcionales de elementosmin/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:
numbersolo admite enteros; precios, calificaciones y medidas sondecimal({ key: "price", type: "decimal", min: 0, precision: 2 }). Ambos admitenmin/maxopcionales;precisiones el máximo de decimales.Opciones fijas →
select:{ key: "condition", type: "select", options: ["new", { value: "used", label: "Used" }] }guarda un solo valor; añademultiple: truepara 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
urlostring. 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" }, valor42; - 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
referenceconmodel: "Media"(multiReferencesolo existe en el nivel superior).
La API pública devuelve el id bajo la clave del campo y añade
<key>_mediacon el archivo ({ id, url, file_name, alt, mime_type, size, is_public, image_transform_base }, una lista paramultiReference), así que el frontend renderizacrafty.image(block.data.image_media, { width: 800, format: "auto" })conalt={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 cadenaphoto_urlno les da nada de eso y se rompe cuando el archivo se mueve.- una imagen (logotipo, hero, avatar,
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 bloquesite-settingsy léelos concrafty.pages.get("site-settings").Idiomas: no dupliques páginas por idioma. Configura los locales del sitio (
configure_locales, por ejemploespor defecto másen), escribe el contenido del locale predeterminado como de costumbre y luego añade cada traducción conupsert_translation(entradas; páginas con sus bloques por id). El frontend pasalocale(createCraftyClient({ locale })o por llamada) y los campos sin traducir recurren al predeterminado. Marca contranslatable: falselos campos que nunca deben cambiar según el idioma (SKU, códigos).meta.translationsenget()lista los locales de un elemento, parahreflang. Cuando la traducción con IA está habilitada para el espacio de trabajo (desde Crafty central) y para el sitio (configure_translationconenabled: true),translate_contenttraduce una entrada o página (con sus bloques) a los locales del sitio sin sobrescribir lo que escribió una persona, yconfigure_translationtambié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
jsonen los tipos de bloque. Si te encuentras queriendo uno, modélalo como repeater ostringList.Referencias a registros:
{ type: "reference", collection: "<slug de la colección>" }(una entrada) omultiReference(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 →
multiReferenceconmodel: "Media"(una sola imagen de portada →referenceconmodel: "Media"); - etiquetas, categorías, productos relacionados, autores →
multiReference(oreference) concollection: "<slug>"apuntando a una colección pequeña de esos registros; una lista corta y fija de etiquetas (tallas, colores) →selectconmultiple: trueo unstringList; - un conjunto corto y fijo de atributos (precio, sku, color) → un campo escalar para cada uno (
decimalpara el precio,selectpara un conjunto fijo de valores); - una lista de elementos ricos por entrada (especificaciones, días de itinerario, preguntas frecuentes de un producto) → un campo
repeateren la colección; - una lista de elementos ricos que solo aparece dentro de una página → un
repeaterde 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):
list_sites→ elige unsite_id(ocreate_site).list_block_types→ conoce los tipos de bloque disponibles (lista los tipos de todos los sitios; usaget_block_typeconsite_idpara el esquema completo de un sitio). ¿Falta uno?create_block_type(site_id,name,fields).create_pageconsite_id,titley, opcionalmente,blocks: [{ block_type_id, data }]. Losdatade cada bloque se validan contra el esquema del tipo de bloque; las claves no declaradas se descartan y se informan en un arreglowarningsde la respuesta.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_pagereemplaza todos los bloques cuando se envíablocks(envía de nuevo cada bloque con suidpara conservarlo).update_entrycrea una revisión.create_pagesiempre empieza comodraft.
¿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.jsones seguro para versionar en git..env(que contiene tu clave de API) debe estar en.gitignore; usa.env.examplecon 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.mdpor completo: marca el contenido concrafty.attrs()(botones y enlaces conhrefFieldsolo 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 aenableVisualEditing()(pasaclientpara sitiosssr; los sitios React/Vue vuelven a obtener datos y se remontan enonRefresh; 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 definirfrontend_url, ejecutaconfigure_visual_editory 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 quepages.*no necesitasiteSlug. 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:submissionssegún se necesite. forms.submit()lanzaBAD_REQUEST→ no se definiósiteSlugencreateCraftyClient(). 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_pageborran 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 aupdate_entrycon el objeto completo combinado. Losdatade 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 enwarnings).create_page/update_pagerechazan los datos de bloque → los datos de bloque se validan contra el tipo de bloque (blocks.2.data.items.1.title: …), los camposnumberson enteros (usadecimalpara precios), los valores deselectdeben ser una de las opciones declaradas, los camposstringadmiten máximo 255 caracteres yblock_type_iddebe pertenecer al mismo sitio. Lee primero el esquema conget_block_type.- Faltan datos en un bloque sin aviso → las claves no declaradas en el tipo de bloque se descartan; revisa el arreglo
warningsde la respuesta. - Modelaste una lista como colección o como campo
json→ usa un tipo de bloque con unrepeater(ostringList) en su lugar; consulta "Modela primero el contenido con tipos de bloque". Dentro de una colección, usarepeater,stringList,selectconmultiple: trueomultiReference(a medios o a otra colección) en lugar dejson. - Adivinaste un nombre o forma de campo (
block.data.titlevsheading, un valor de select,imagevsimage_media.url) → generacrafty.types.tsconcrafty typesy 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 unreference(multiReferencepara varias) conmodel: "Media"; renderizacrafty.image(<key>_media, { width, format: "auto" }). Cambia un campo existente conupdate_block_type/update_collectiony vuelve a guardar el contenido con los ids de medios. - Servir la imagen original (
<img src={media.url}>) → las subidas grandes perjudican el LCP. Usacrafty.image(media, { width, format: "auto" })máscrafty.srcset(); recurre aurlcuando las transformaciones no están disponibles. - Imágenes subidas sin texto alternativo → define
altconupdate_mediapara cada imagen de contenido (list_mediamuestra cuáles tienenalt: null) y renderiza<key>_media.alten 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 necesitanpublish_entry. - Confundir los dos tokens → la
apiKeydel 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, copianode_modules/@craftware/crafty-sdk/skills/crafty-cmsencima.
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_editory verificación.