LUMBRE PARA AGENTES · DOS PUERTAS
Dos puertas: consultar es libre, crear requiere clave.
Elige tu puerta
- CONSULTAR — Lumbre Knowledge Gateway (público, gratuito, sin registro ni claves): tu agente busca, lee y cita el catálogo educativo publicado — MCP en
/mcp/knowledgey REST en/api/v1/knowledge. Única condición: atribuir la fuente. Documentos para máquinas:/llms.txt·/agents.txt·lumbre-agent.json· Términos: /agentes/terminos. - CREAR — esta API de autoría (necesita clave): publicar y versionar objetos, laboratorios y juegos, organizar categorías/cursos/rutas, declarar habilidades y auditar/mantener el catálogo en lote. Todo lo que sigue en esta página pertenece a esta puerta.
Lumbre abre una API para agentes con capacidad de ejecutar código (ChatGPT, Claude, Gemini o el tuyo propio): el agente construye la lección, la publica por un único POST y se autocorrige con los errores que le devolvemos —y además crea categorías, cursos y rutas y coloca cada objeto o libro en su sitio. Sin abrir el navegador, sin subir ZIPs.
Para personas · dos pasos
- Entra al Estudio de objetos, baja hasta «API para agentes» y crea una clave (cópiala: se muestra una sola vez).
- Abre un agente que pueda ejecutar código y ejecutar el pedido de abajo. Pega el prompt, cambia el tema y la clave, y deja que trabaje: al final te dará la URL del objeto publicado.
Para agentes · la API en 3 llamadas
- Esquema completo (público, sin clave):
GET https://filosofia.lumbre.lat/api/v1/objects/schema— tipos de bloque, campos, límites y reglas en JSON. - Publicar:
POST https://filosofia.lumbre.lat/api/v1/objectscon cabeceraAuthorization: Bearer <clave>y cuerpo JSON{"object": {format, meta, blocks}, "visibility": "PUBLIC"|"UNLISTED", "assets": {"assets/nombre.png": "<base64>"}, "cover": "<base64>"}. También se acepta el documento pelado (conformatyblocksa nivel raíz).coveres opcional: la portada de la ficha (ver Portada). - Respuestas:
201→{ok:true, slug, url, version, assets, minutes, cover:{url,source}}(listo).422→{ok:false, errors:[{block, field, message}]}: corrés esos puntos y reintentás;401/403→ la clave no sirve o le falta permiso;429→ esperá y reintentá.
Re-POSTEAR un objeto con el mismo título no lo duplica: crea una versión nueva de la misma ficha (idempotente por slug). Las imágenes se envían en base64 dentro de assets; para video en línea NO subas nada: usá el bloque embed con el id de YouTube o Vimeo.
Leer y mejorar objetos que ya existen
La misma clave también lee lo que la cuenta puede administrar, para que el agente mejore un objeto y lo republique sin duplicarlo: re-POSTear con el mismo slug crea una versión nueva de la misma ficha.
- Listar:
GET /api/v1/objects→ los objetos de la cuenta (slug,title,state,visibility,published,url). Filtro?q=texto; el personal puede verlos todos con?all=1. - Leer uno:
GET /api/v1/objects/{slug}→ el documento publicado enobject({format, meta, blocks}), laversion, lavisibilityy losassets(con suurl). Añade?assets=1para recibir ademásassets_b64(el base64 de cada imagen/audio). - Mejorar y republicar: edita el
objectque leíste y vuelve aPOST https://filosofia.lumbre.lat/api/v1/objectscon el mismoslug(e"assets": …assets_b64…si tiene imágenes, para no perderlas). Responde201con laversionincrementada.
Cabecera Authorization: Bearer <clave> en las tres llamadas. Sólo se lee lo que la cuenta puede administrar (su dueño, el personal, o admin/coordinador de su organización); si no, 403. Límite: 300 lecturas por hora por clave.
Portada · la imagen de la ficha (cover)
Cada objeto tiene una portada: la imagen que se ve en el catálogo, en las tarjetas de ruta y al compartir (/media/object/{id}). Es propiedad de la ficha, no un bloque del contenido, por eso no va dentro de blocks: se envía con la clave "cover". Lumbre la recorta y normaliza a JPG 8:11 (640×880).
- Al crear o actualizar:
"cover": "<base64>"(admite prefijodata:image/png;base64,) o"cover": "assets/portada.png"señalando un archivo incluido enassets. Formatos: jpg, png, webp, gif; hasta 15 MB. - Cambiarla después, sin reescribir el objeto:
POST /api/v1/objects/{slug}/patchcon{"ops":[{"op":"set_cover","src":"assets/…"}]}— la imagen puede ser un recurso ya publicado o una nueva enassets— o, más directo,{"cover": "<base64>"}omitiendoops. El documento NO se toca. - Desde MCP: el mismo
coverenlumbre_create_object/lumbre_update_object/lumbre_patch_object, olumbre_upload_assetconis_cover:truepara promocionar una imagen ya subida. - Se conserva: si republicas o parcheas sin mandar
cover, la portada actual queda intacta (Lumbre sólo dibuja una de identidad la primera vez que el objeto no tiene ninguna). No hace falta reenviar la imagen en cada versión. - Leerla:
GET /api/v1/objects/{slug}devuelvecover:{url,source,has_cover}, y con?assets=1tambiéncover_b64para poder reenviarla o editarla. En MCP:lumbre_get_object→coverycover_b64. El audit?missing=coverlista los objetos sin portada.
La portada NO es un recurso del documento: si mandas una imagen como assets/foto.png y la usas en un bloque image, eso no cambia la portada. Para eso existe cover / set_cover. Consejo: una portada real (foto, ilustración del tema) rinde mucho más que el dibujo automático; puedes tomarla de bancos libres y enviarla en base64.
Libros de lectura · lecturas cortas, libros largos y cómics/PDF
Además de objetos de aprendizaje, la misma clave de agente crea y edita LIBROS (la entidad books que consume la biblioteca): lecturas cortas, libros largos de texto y cómics o PDF de lectura directa. Reutiliza el mismo pipeline del Estudio (importadores txt/pdf/docx/epub, normalizador, control de calidad y guardas de derechos): misma validación, mismas restricciones. Esquema completo (público, sin clave): GET https://filosofia.lumbre.lat/api/v1/books/schema.
- Tres tipos (
kind):book= libro largo de texto;short= lectura corta (content_type=short_read);comic= PDF de lectura directa (read_mode=pdf, se sirve el archivo tal cual sin extraer texto). - Crear:
POST /api/v1/booksconAuthorization: Bearer <clave>y{"title", "author"?, "kind", "visibility", …}. El contenido va en UNA de tres formas:"text"(plano/markdown, los capítulos se parten solos),"chapters": [{title, text|blocks:[{type,text}]}], o"file": {"name": "…pdf", "content_base64": "…"}(para cómic/escaneado añade"direct_pdf": true). Paravisibility:"PUBLIC"hace falta permisocan_publishy"copyright_confirm":"1". - Portada:
"cover": "<base64>"(jpg/png/webp/gif, admitedata:image/…;base64,); Lumbre la normaliza a JPG y la sirve en/media/book/{id}. Igual que los objetos: si no mandascoveral editar, la actual se conserva. - Validar sin publicar:
POST /api/v1/books/validatecorre el control de calidad y devuelve el veredictogreen/yellow/red, palabras/capítulos y el motivo de rechazo (un texto de baja calidad se RECHAZA, como en el Estudio). - Aguanta agentes torpes: si el libro sí se guardó pero algo menor no se aplicó (un
kindmal escrito quedó comobook, una portada ilegible se omitió), la respuesta trae"warnings":[…]en español para que el agente se autocoriga; los errores de verdad (sin contenido, calidad roja, archivo falso,PUBLICsin confirmar derechos) devuelven4xxcon el motivo y nunca500. El texto con etiquetas<script>se guarda y se sirve escapado: no inyecta código en el lector. - Listar y leer:
GET /api/v1/books(los de la cuenta;?q=texto; personal?all=1) yGET /api/v1/books/{slug}(metadatos;?content=1incluye los capítulos,?cover=1añadecover_b64). Sólo dueño o personal. - Editar:
POST /api/v1/books/{slug}(oPOST /api/v1/bookscon el mismo"slug", idempotente) cambia metadatos, portada, taxonomía y reemplaza el TEXTO; no reemplaza el DOCUMENTO subido (para eso, crea de nuevo o usa el Estudio).
Equivalente MCP: lumbre_list_books, lumbre_get_book, lumbre_create_book, lumbre_update_book, lumbre_validate_book (usan los mismos scopes objects:read/create/update). Un libro no es un objeto de aprendizaje: son entidades distintas con pipelines distintos; lumbre_upload_asset con un PDF sólo lo adjunta a un objeto, no crea un libro.
Mantenimiento en lote · auditar huecos, buscar video y parchear sin reescribir
Para mejorar objetos ya publicados sin mover el documento completo (leer 60 bloques para añadir un video es caro), la API ofrece un ciclo de tres llamadas: auditar → buscar/validar → parchear. El parcheo aplica operaciones ordenadas sobre el payload actual, revalida y republica una versión nueva (mismo slug/url), reempaquetando los assets existentes — solo envías los archivos nuevos. Si una operación falla, no se publica nada.
- Auditar huecos:
GET https://filosofia.lumbre.lat/api/v1/objects/audit(requiere clave) —?missing=video,image,scored(csv; por defectovideo; valores válidos:video,image,audio,chart,scored,interactive,description,tags,cover),&subject=,&q=,&limit<=50,&page. Devuelve cada objeto con sus aspectos faltantes y las llamadas exactas para corregirlo. Las cuentas corrientes auditan sus objetos; el personal, todo el catálogo. - Buscar y validar video (Lumbre lo hace por ti):
GET https://filosofia.lumbre.lat/api/v1/media/video—?q=palabrasdevuelve candidatos VIVOS (re-validados por oembed oficial) ordenados por relevancia con el primero marcadorecommended;?id=XXXX&provider=youtube|vimeoverifica un id concreto. Nunca inventes ids: solo sirven los devueltos o verificados aquí. - Validar imagen (MISMO principio, aplicado a fotos):
GET https://filosofia.lumbre.lat/api/v1/media/image—?src=URL|data-URIdescarga la imagen y confirma que es una imagen real y viva: mime detectado de los bytes (no del nombre), formato, dimensiones en píxeles, peso y si es apta como portada (jpg/png/webp/gif). Detecta páginas de error disfrazadas, links muertos y formatos no soportados antes de armar un bloqueimageo unacover. Segundo modo:?object=slug|id&src=assets/nombre.pngcomprueba que un recurso ya publicado del objeto existe en disco (útil antes deset_coveroadd_image). Lumbre no incrusta URLs externas en los bloques: la imagen debe subirse como recurso del objeto. - Parchear (edición parcial):
POST https://filosofia.lumbre.lat/api/v1/objects/{slug}/patchcon{"ops":[…], "assets":{"assets/nuevo.png":"<base64>"} (solo archivos NUEVOS), "cover":"<base64> | assets/…", "dry_run":false}. Operaciones:{"op":"add_video","query":"…"}(Lumbre busca, valida y inserta el bloqueembed; opcionalvideo_idpara fijar uno exacto),add_block,replace_block,remove_block,set_meta,add_image,{"op":"set_cover","src":"assets/…"}(elige la portada entre las imágenes del objeto),{"op":"set_world_connections","connections":[…]}(reemplaza la raíz opcional de conexiones) y{"op":"add_world_connection","connection":{…}}(fusiona una cápsula sin duplicar). Los bloques se direccionan porindex(0-based) oid; losembedsolo porindex. Para cambiar solo la portada mandacovery omiteops. Usadry_run:trueprimero: valida y devuelve eloutlinesin tocar nada.
Equivalente MCP (con tu token MCP, que es independiente de la clave REST): lumbre_audit_content → lumbre_find_video → lumbre_patch_object. Flujo recomendado: audit?missing=video → elegir un objeto → media/video?q=… → patch con dry_run:true → reenviar sin dry_run para publicar la versión nueva. Esto complementa el ciclo «leer → reescribir → re-POST» de arriba: úsalo cuando solo quieras tocar una parte, sin reenviar el documento ni las imágenes enteras.
Cura de grupos · analiza y arregla un curso, categoría o ruta ENTEROS de un golpe
El ciclo anterior (audit → buscar → parchear) actúa sobre un objeto. La cura de grupos sube un nivel: resuelve TODOS los objetos que viven dentro de un curso, una categoría (con sus cursos hijos y rutas) o una ruta (colección), los analiza en bloque (huecos de contenido y salud real de sus medios) y aplica una tanda de operaciones a todo el grupo a la vez, uno a uno y de forma aislada (un objeto que falla no aborta los demás). Es «analízalo todo y arregla los huecos de ese grupo» sin tener que listar y parchear objeto por objeto. Esquema machine-readable (público, sin clave): GET https://filosofia.lumbre.lat/api/v1/groups/schema.
- Alcance (
scope) y referencia (ref=slug o id):route= una ruta (colección);course= un curso (círculokind=course) y todas las rutas asignadas a él;category= una categoría (círculokind=category) de la que se desciende a subcategorías, cursos y todas sus rutas. - Analizar el grupo:
GET https://filosofia.lumbre.lat/api/v1/groups/{scope}/{ref}/analysis(requiere clave) — query?aspects=video,image,cover…(csv; por defecto todos),&check_media=1(re-valida los videos por oembed, revisa los recursos «assets/…» en disco y la portada: caro, presupuesta hasta 60 objetos por petición),&subject=,&q=texto,&limit<=100,&page. Devuelvesummarypor aspecto (broken_video,missing_asset,no_cover,with_gaps) y una lista paginada de objetos con sus huecos (missing/present) y, sicheck_media, los medios rotos de cada uno. - Ajustar el grupo en tanda:
POST https://filosofia.lumbre.lat/api/v1/groups/{scope}/{ref}/applycon{"ops":[…], "match"?:{"missing":["video"]| "slugs":["a","b"]}, "exclude_slugs"?:["…"], "dry_run":true, "max":50}. Lasopsson LAS MISMAS que acepta/api/v1/objects/{slug}/patch(add_video, add_block, replace_block, remove_block, set_meta, add_image, set_cover, set_curriculum, set_world_connections…).match.missingselecciona los objetos con ese hueco;match.slugslos exactos; sinmatch= todo el grupo. Empieza SIEMPRE condry_run:true(previsualiza conteos y outlines sin publicar nada) y reenvía condry_run:falsepara crear una versión nueva por objeto modificado (mismo slug/id). La tanda NO aceptaassets(subir una imagen se hace por objeto con su patch individual). Responde207cuando la tanda mezcla éxitos y fallos; cada resultado trae su propioerror. - Permisos dobles: la cuenta dueña de la clave debe poder administrar el grupo (ser su dueño, admin del círculo o personal), y cada objeto sólo se modifica si su dueño/personal/organización lo permite; los demás se omiten con conteo
skipped_permission(nunca un 403 colectivo). Límites: analysis 300/h, apply 100/h por clave.
Equivalente MCP: lumbre_analyze_group (scope objects:read) → lumbre_apply_group (scope objects:update), con los mismos scope/ref/ops/match/dry_run. Flujo recomendado: analyze_group?check_media=true para ver qué huecos y medios rotos tiene el curso → apply_group con dry_run:true y {"ops":[{"op":"add_video","query":"…"}],"match":{"missing":["video"]}} → revisa conteos → reenvía con dry_run:false.
Tutor interactivo · la raíz opcional companion
Cada objeto puede ofrecerse con un tutor opcional que recorre el mismo contenido: presenta, señala, pregunta con los ejercicios que ya existen, da pistas progresivas y ofrece los refuerzos del propio objeto. No es un segundo objeto ni ejercicios duplicados, y no usa IA: todo es determinista. El alumno siempre elige «Modo normal / Con tutor» al entrar y puede pausar o desactivar en cualquier momento.
- No hace falta declararlo: si el objeto es interpretable (≥2 bloques y ejercicios o un objetivo), Lumbre infiere la capacidad y ofrece el tutor.
companionsolo se necesita para apagarlo ("enabled": false), fijar la frecuencia o aportar guion y microretos. - Forma mínima:
"companion": { "enabled": true, "mode": "auto" }a nivel raíz junto aformat/meta/blocks.mode:auto(acompaña la lectura) oguided(el tutor conduce paso a paso). - Vocabulario cerrado: el esquema vivo de la raíz está en
GET https://filosofia.lumbre.lat/api/v1/objects/schema(clavescompanionycompanion_json_schema): enums exactos detriggers(eventos:block.enter,exercise.incorrect,checkpoint.failed…),actions(say,point_to,give_hint,offer_reinforcement…), animaciones y límites. No inventes nombres: la API rechaza con422cualquier trigger, acción o referencia a un bloque/refuerzo inexistente, con el campo culpable enerrors[].field. - Opcional:
"script"(≤40 intervenciones{trigger, action, block|exercise|reinforcement|challenge, text, animation, delay, once}) y"challenges"(≤20 microretos{id, type, question, options?, answer, hint?, explanation?}que NO puntúan ni duplican los ejercicios del objeto).
El texto del tutor es texto plano (sin HTML) y sus explicaciones salen del propio objeto (explanation/evidence de los ejercicios y raíz reinforcements): una sola fuente de verdad. Vista previa sin tocar preferencias: abre la ficha del objeto con ?tutor=1 (forzar) o ?tutor=0.
Conexiones · la raíz opcional world_connections
Cada objeto puede llevar cápsulas cortas que muestran dónde aparece ese mismo conocimiento fuera de la lección: en el arte, la historia, las personas, la vida cotidiana, la naturaleza, la tecnología, los oficios, los lugares y las culturas. En la ficha aparece un solo botón «Conexiones» que abre una ventana superpuesta: se cierra sin perder el punto de lectura. Es estrictamente opcional: un OA puede tener una, varias o ninguna conexión — y ninguna es mejor que una decorativa.
- Forma:
"world_connections": [ {"id", "kind", "title", "explanation", "reference": {"name", "detail"?}, "media"?, "action"?, "links"?, "source_url"?, "source_note"? } ]a nivel raíz junto aformat/meta/blocks(≤8 cápsulas). El vocabulario completo y los límites están enGET https://filosofia.lumbre.lat/api/v1/objects/schema, claveworld_connections. - Qué va en cada cápsula (los 5 puntos):
title+explanation= qué aspecto concreto del OA se manifiesta en el ejemplo (no una biografía ni una lección paralela);reference= la obra/hecho/persona/lugar/fenómeno real e identificable;media= UN recurso cuando ayuda (imagen/audio localassets/…o videoyoutube|vimeoconattributionobligatoria);action= una pregunta o acción breve para observar, comparar, escuchar, localizar o aplicar;links= ≤2 salidas{"oa": "slug-de-otro-objeto"}para profundizar (Lumbre sólo muestra los que existen y están publicados). kindcerrado:art(Arte),history(Historia),people(Personajes),daily_life(Vida cotidiana),nature(Naturaleza),technology(Tecnología),crafts(Oficios),places(Lugares),cultures(Culturas). La API rechaza con422cualquier otro valor, título duplicado osource_urlde un dominio no verificable (lista blanca: Wikipedia, Biblioteca digital mundial, gob.mx/SEP/INEGI, NASA, WHO, museos, etc.).- Criterio de calidad (la regla dura): nada de asociaciones decorativas ni forzadas, ningún dato inventado, y ningún recurso cuya procedencia no puedas verificar. Para acontecimientos actuales, verifica la vigencia antes de publicar. Si la conexión no pasa «¿qué aspecto del OA se ve aquí, y puedo probarlo?», no la incluyas.
- Reutiliza antes de crear: apunta con
linksa OA ya publicados del catálogo y reutiliza imágenesassets/…existentes; mantén las conexiones con{"op":"set_world_connections"}o{"op":"add_world_connection"}(fusiona porid/título, nunca duplica).
Equivalente MCP: la raíz viaja en lumbre_create_object / lumbre_update_object (documento completo) y las dos ops anteriores en lumbre_patch_object. La cápsula no es un segundo objeto: enriquece el OA y ofrece una salida opcional hacia contenidos más profundos.
Fundamento oficial · la raíz opcional curriculum
Cada objeto puede declararse contra un requisito oficial del plan de estudios (SEP u otro sistema). El fundamento es hoy parte estructural del OA: viaja dentro del propio documento cuando un nodo maestro se sincroniza con sus esclavos, y se re-materializa en el destino por el code estable del requisito (no por ids locales). Es estrictamente opcional y aditivo: un OA sin fundamento funciona igual. Vocabulario y límites: GET https://filosofia.lumbre.lat/api/v1/objects/schema, clave curriculum.
- Forma:
"curriculum": { "framework": {"authority", "program", "country" (ISO-3166 alfa-2), "level" (primaria|secundaria|media_superior|superior|otro), "version"?, "source_url"?, "source_doc"?, "consulted_at"? (AAAA-MM-DD)}, "requirements": [ {"code", "text_official"?, "req_type"?, "relationship"?, "coverage"?, "source_url"?, "source_page"?, "rationale"?, "evidence"?} ] }a nivel raíz junto aformat/meta/blocks. La identidad portable del requisito escode(≤80, único a nivel mundial); los demás campos alimentan el marco y la ficha. - Cada requisito se referencia por
code: el nodo destino resuelve el requisito por su código y, si no existe, lo crea junto con su marco (el esclavo queda auto-suficiente sin re-crear catálogos a mano). Nunca inventes uncode: usa los planes reales víalumbre_list_frameworks/lumbre_search_requirements. - Página sólo si está confirmada:
source_pagese muestra en la ficha como «pág. N» únicamente cuando lo verificaste contra el PDF/plan oficial; elsource_urles la liga al documento oficial real. Sin verificación, se omite la página — jamás se presume. - Ética de la verificación (no negociable): todo fundamento que declara un agente se guarda como
agent_proposedy nunca se autocertifica comoverified/human_verified; ese estado sólo lo hereda una sincronización desde un maestro de confianza o lo fija una persona. Una alineación ya verificada localmente no se degrada al re-importar. La ficha conserva el aviso de que Lumbre no está avalado por la SEP y nunca presenta unagent_proposedcomo verificado. - Mantener:
{"op":"set_curriculum","curriculum":{"framework"?,"requirements":[{"code",…}]}}reemplaza ENTERA la raíz (los requisitos van porcode); mandacurriculum:nullorequirements:[]para retirarla.lumbre_get_objectdevuelve el fundamento ya re-hidratado para que puedas leerlo y reenviarlo.
Equivalente MCP/API: la raíz viaja en lumbre_create_object / lumbre_update_object (documento completo) y en lumbre_patch_object con set_curriculum; por REST se declara en el cuerpo de POST https://filosofia.lumbre.lat/api/v1/objects y en la operación homónima. Distinto de la ligera meta.alignment (país/nivel/grado sueltos): curriculum es el vínculo estructural trazable a requisitos oficiales concretos.
Descubrimientos Lumbre · diseña la recompensa junto al contenido
Cuando un estudiante acierta todas las actividades evaluables de un objeto, colecciona su descubrimiento (cromo + puntos según Tier de dificultad). No hay API para regalarlos — se ganan con evidencia —, pero sí puedes diseñarlos al crear o parchear el objeto: declara en meta.discovery cómo debe verse y a qué álbum pertenece.
- Formato (todo opcional):
"meta": {…, "discovery": {"title": "Saturno", "description": "≤500", "emoji": "🪐", "palette": "#0288d1", "type": "cromo", "tier": 3, "collection": "Sistema Solar"}}. Sindiscovery, Lumbre lo genera del título/materia y calcula el Tier por edad recomendada y nº de actividades evaluables. - Álbumes:
collectionagrupa los cromos de varios objetos (nombre temático estable); al reunir todas las piezas (≥3) el usuario recibe la copa del álbum. La materia también genera su colección automática. - Regla de oro: un OA sin bloques puntuables (mcq, true_false, fill_blank, match, sort…) no puede otorgar descubrimiento: si diseñas contenido coleccionable, incluye evaluación. Y no inventes tieres altos por estética: el Tier mide dificultad demostrada, no valor de la persona.
- Cómo se viven: al completar el objeto el Player celebra el descubrimiento en pantalla (emoji, Tier y puntos); esos puntos alimentan el nivel del usuario, su escritorio y sus estadísticas.
Copy de marketing · la frase que da ganas de entrar
Cada OA y cada curso guarda en su cabecera de base de datos un gancho de marketing (campo marketing, ≤280 caracteres): la línea que se usa en catálogo, meta description, Open Graph y campañas. Es obligatoria en la ficha pero opcional en tu envío: si no la mandás, Lumbre la autogenera del material real del objeto (título, descripción, objetivo, materia, duración). Es aditiva — nunca sustituye al description ni al objective.
- Objeto de aprendizaje: declaramelo en el documento —
"meta": {…, "marketing": "Aprende a leer tus nóminas sin miedo · Finanzas · en 6 minutos"}— porPOST https://filosofia.lumbre.lat/api/v1/objects,lumbre_create_object/lumbre_update_objectolumbre_patch_object. Lo que mande el agente manda sobre la derivación: nadie vende mejor el contenido que quien lo crea. Si lo omitís, se deriva solo, de forma determinista (mismo contenido → mismo gancho), para que los re-POST idempotentes no muevan textos ya publicados. - Curso / ruta: mismo campo en
POST /api/v1/structure/coursesy…/routes({"name", "marketing"?, …}); sin él, se compone del nombre y su objetivo/descripción. - Se respeta tu cura: una vez escrito, republicar el objeto sin
marketingNO lo pisa — conserva el que ya estaba. Para regenerar desde cero hay que mandar el valor explícito o pasar por la ficha de edición. - Qué buscamos al escribirlo: una promesa concreta y en segunda persona, con garra, que provoque entrar; nada de relleno («en este objeto vamos a ver…»). Sin HTML ni entidades — se rechazan en la validación.
Organizar el contenido · crear estructura y colocar
Con la misma clave el agente también arma la estructura del catálogo (categorías, cursos y rutas) y coloca objetos y libros dentro, sin abrir el navegador. Esquema completo (público): GET https://filosofia.lumbre.lat/api/v1/structure/schema. Todo es idempotente por slug: re-POSTear el mismo slug devuelve lo existente (created:false) en vez de duplicarlo.
- Categoría:
POST /api/v1/structure/categories—{"name", "slug"?, "description"?, "parent"?, "privacy"?}→{category_id, slug, url}. - Curso:
POST /api/v1/structure/courses—{"name", "slug"?, "category"?}(la categoría contenedora, por slug o id) →{course_id, slug, url}. - Ruta:
POST /api/v1/structure/routes—{"name", "slug"?, "course"?, "stages"?:["Inicio",…], "objective"?, "difficulty"?, "visibility"?}→{route_id, slug, url}. Si indicáscourse, la ruta queda asignada a ese curso. - Colocar un ítem:
POST /api/v1/structure/routes/{slug}/items—{"type":"object"|"book", "ref":"slug-o-id", "stage"?:"nombre-o-id", "note"?}. La etapa por nombre se crea si no existe. - Asignar ruta a curso:
POST /api/v1/structure/courses/{slug}/routes—{"route":"slug-o-id"}. - Matricular alumnos (roster):
POST /api/v1/structure/courses/{slug}/roster—{"students":[{"identifier":"correo|usuario"}|{"name":"Nombre"}]}para inscribir cuentas existentes o crear provisionales (devuelvetemp_passworduna sola vez al crear), o{"csv":"nombre,usuario,correo"}(una fila por alumno, ≤500) para carga masiva. Idempotente: quien ya está inscrito no se duplica. Responde201con{created, joined, missing, students[]}(oreporten modo csv). Debe poder administrarse el curso/aula. - Ver miembros:
GET /api/v1/structure/courses/{slug}/roster→{count, members:[{user_id, name, username, role, joined_at}]}(sin correos). Lo lee quien lo administra o pertenece.
Atajo: al publicar un objeto (POST https://filosofia.lumbre.lat/api/v1/objects) podés incluir "route" (y "stage"/"note") en el mismo cuerpo y se coloca directamente en esa ruta sin una segunda llamada; la respuesta trae route:{placed,…}. La cuenta dueña de la clave debe poder administrar el curso/la ruta (ser su dueño, admin del círculo o personal).
Rutas vivas · teje conexiones entre objetos (la web de «¿qué hago ahora?»)
Cada objeto publicado termina con una sección «Rutas vivas»: continuaciones tipadas hacia otros objetos, cada una con la pregunta concreta que responde («¿qué necesito entender antes?», «¿qué puede salir mal?», «¿cómo lo aplico?») y el motivo. Esas aristas viven en el grafo content_relations y se tejen por MCP — ningún agente debe tocar la base de datos a mano. Un agente con scopes relations:read/relations:write puede recorrer el catálogo, hallar huérfanos y tejer solo, en loop.
- Ciclo autónomo de tejido:
lumbre_graph_gaps(publica · halla objetos huérfanos o con pocas aristas) →lumbre_graph_neighbors(lee qué continuaciones YA ofrece un nodo, en ambas direcciones, para no duplicar) → decide con criterio lector →lumbre_weave_edges(teje un lote de hasta 100 aristas, condry_run:truepara validar sin persistir) → repite. - Tejer DESDE UNA SEMILLA (tela de araña): si el pedido es «teje desde X» (p. ej. «desde el sol»), usa
lumbre_expand_frontierconseed:"X"y una lista de temas candidatos: resuelve la semilla por palabra completa (no por subcadena, para no confundir «sol» con «soltar») y te dice, por cada tema, silink(ya existe),review-then-create(match débil, a leer) ocreate(falta). Crece en anillos con límite duro de 3 anillos de profundidad y 50 objetos nuevos por corrida. Al crear, el objeto debe traer portada de Wikimedia (licencia libre, nunca IA), video verificado si aplica, y cerrar con un bloquelink«Fuentes» que cite referencias serias y verificables; sin fuente confiable, no se inventa: se acota el alcance o se deja en borrador. - Qué es una arista:
{from, to, relation, question, note}.from/toaceptan id numérico o slug; ambos objetos deben estar publicados y serPUBLIC/UNLISTED.question(obligatoria) es la pregunta del lector en primera persona;noteexplica por qué el destino la responde. - Verbos disponibles (
relation):requires(prerrequisito: «¿qué necesito antes?»),next(siguiente paso: «¿qué hago ahora?»),extends(profundiza),related(alternativa o camino paralelo),explains/practices/applies/teachessegún el caso. El orden mostrado prioriza requires → next → explains → practices → applies. - Idempotente: tejer la misma
from+to+relationdos veces no duplica: actualiza la pregunta y el motivo. Para destejer una arista equivocada:lumbre_unweave_edgecon el verbo exacto. - Regla de calidad (no negociable): teje solo conexiones que respondan una pregunta real del lector al terminar el objeto origen. Nada de «related» de relleno ni hubs arbitrarios: primero lee los dos objetos (
lumbre_get_object) y prefiere reutilizar contenido existente antes de proponer crear nuevo.
Así se construyeron las cinco rutas de Rutas Vivas (primer empleo, dinero cotidiano, trámites MX, salud emocional, herramientas digitales e IA) bajo /collections/ruta-viva-*: 277 aristas OA↔OA con 170 preguntas, casi siempre enganchando objetos ya publicados en lugar de crearlos.
Prompt listo para soltar a tu agente (con el MCP conectado)
Crea antes un token MCP solo con relations:read, relations:write y objects:read en el Estudio (o por consola: php bin/mcp-token.php create --scopes=relations:read,relations:write,objects:read --label="tejedor"), conéctalo al agente y pégale este prompt. El agente recorre los huecos y teje solo; tú solo revisas los conteos.
Habilidades · la capa semántica del aprendizaje
Con la misma clave el agente declara para qué sirve cada objeto: habilidades (skills) con su grafo de prerrequisitos, y su relación OA↔habilidad (teaches/practices/evaluates) y bloque↔habilidad (la evidencia de cada respuesta). Todo es aditivo: un objeto publicado sin habilidades sigue funcionando igual. Esquema completo (público): GET https://filosofia.lumbre.lat/api/v1/skills/schema.
- Crear/actualizar:
POST /api/v1/skills—{"code"?:"MATE_FRACC_EQ", "name", "description"?, "subject"?, "grade"?, "difficulty"?, "prerequisites"?:[code…], "associate"?:{"oa":"slug","role":"teaches","primary":true}}→201 {skill_id, skill, associated_object_id}. Idempotente porcode; se puede enviar sóloassociatepara enlazar una habilidad existente. - Listar:
GET /api/v1/skills—?q=texto&subject=&status=con paginación (per_pagemáx. 96). - Detalle:
GET /api/v1/skills/{code|id}→{skill, prerequisites, related, objects:[{slug,title,role,is_primary}]}— el entorno de grafo de la habilidad. - Validar SIN persistir:
POST /api/v1/skills/validate→{ok, valid, errors:[{field,message}]}. Corre la misma normalización que la creación (formato decode, longitud, ciclo de prerrequisitos). - Enriquecer un OA publicado:
POST /api/v1/objects/{slug}/skills—{"add"?:[{"skill":code,"role"?,"primary"?,"weight"?}], "remove"?:[code…], "blocks"?:{"block_ref":code}}→{ok, applied, skills}.blocksconvierte bloques evaluables en evidencia de una habilidad concreta. - Dominio del alumno:
GET /api/v1/my-mastery(con sesión) → perfil 0-100 por habilidad con banda (iniciando/desarrollando/competente/dominio) y hint amable.
También podés declarar las habilidades dentro del propio objeto: meta.skills (1-12, cada una con code/name/role) y la clave "skill" en los bloques evaluables; al publicar, el sincronizador las enlaza solo. Caras web para personas: /skills (catálogo), /skill/{code} (detalle con grafo) y /mi-dominio.
Descubrir, validar y previsualizar (antes de publicar)
Además de publicar, la API expone endpoints públicos de descubrimiento (sin clave) y un validador sin persistencia, para que el agente se autocorrija antes de crear nada. Hay un visor interactivo en GET https://filosofia.lumbre.lat/api/docs y el documento OpenAPI 3.0.3 en GET https://filosofia.lumbre.lat/openapi.json.
- Tipos de bloque:
GET https://filosofia.lumbre.lat/api/v1/block-types— lista resumida (type,label,version,description,scored,schema_url). Público. - Detalle de un bloque:
GET https://filosofia.lumbre.lat/api/v1/block-types/{type}— su JSON Schema (draft 2020-12), unexampleválido,when_use/when_noty la forma de laresponsedel alumno. Un tipo inexistente devuelve404conerror.code = "UNKNOWN_BLOCK". - Taxonomías:
GET https://filosofia.lumbre.lat/api/v1/taxonomies— materias, niveles cognitivos, niveles educativos, dificultades, tipos/visibilidades de ruta y la lista de tipos de bloque. Público. - Validar SIN publicar:
POST https://filosofia.lumbre.lat/api/v1/objects/validatecon{"object": {…}, "assets"?: {…}}(requiere clave) →{ok, valid, blocks, errors:[{block, field, message}], missing_assets}. Corre el mismo validador que la publicación y, si mandasassets, comprueba que cadaassets/…referenciado esté presente. No crea nada. - Borrador + previsualización: añade
"draft": truealPOST https://filosofia.lumbre.lat/api/v1/objects. El objeto se guarda enstate DRAFT(no es público; sólo lo ve su dueño/personal) y la respuesta traepreview_url(/object/{slug}?preview=1) con un banner de borrador. Para publicar, re-POSTea el mismoslugsindraft. - Descubrir estructura:
GET /api/v1/structure/categories,/coursesy/routes(requieren clave) listan lo que la cuenta puede administrar, para saber dónde colocar contenido antes de crearlo.
Flujo recomendado del agente: block-types → construir el documento → validate → corregir errors[] → POST /objects con draft:true → revisar en preview_url → re-POSTear sin draft para publicar.
Códigos de error estándar
Los errores de la API traen un cuerpo máquina-legible {"error": {"code", "message", "details"?}}. Códigos canónicos:
INVALID_INPUT— el cuerpo no es JSON válido o falta el documento.VALIDATION_ERROR— el documento no pasó el validador (vererrors[]/details).NOT_FOUND— no existe el recurso (objeto, ruta…).UNKNOWN_BLOCK— se pidió un tipo de bloque que no existe.PERMISSION_DENIED— la clave no puede hacer eso (403).RATE_LIMITED— se superó el límite por hora (429): espera y reintenta.SERVER_ERROR— fallo interno (500).
Laboratorios · crea contenido interactivo con el formato lumbre-lab/1.0
Un laboratorio es un documento JSON declarativo (sin JS propio): actividades con componentes de un catálogo cerrado que la plataforma renderiza y valida. La misma clave de agente crea, actualiza y versiona laboratorios.
- Descubrir el motor:
GET https://filosofia.lumbre.lat/api/v1/labs/schema(público) → formato, reglas demeta, y el catálogo completo de componentes (native, motor y primaria) con susparams. Consúltalo antes de escribir cualquier definición. - Listar:
GET https://filosofia.lumbre.lat/api/v1/labs(público) → laboratorios publicados. Filtros?level=primaria|secundaria|preparatoria,&subject=,&grade=,&difficulty=,&q=texto. - Leer uno:
GET https://filosofia.lumbre.lat/api/v1/labs/{slug}→ la definición completa endefinition(con suuidyversion). Edita y re-POSTea con el mismouidpara crear una versión nueva sin duplicar el laboratorio ni perder el progreso de los estudiantes. - Validar SIN publicar:
POST https://filosofia.lumbre.lat/api/v1/labs/validatecon{"lab": {…}}→{ok, errors[]}con errores de máquina tipoactivities[2].components[0]: componente no soportado. Corre el mismo validador que la publicación. - Publicar:
POST https://filosofia.lumbre.lat/api/v1/labscon cabeceraAuthorization: Bearer <clave>y{"lab": {format, meta, activities}, "assets"?: {"assets/imagen.png": "base64…"}, "visibility"?: "PUBLIC|UNLISTED|CIRCLE|PRIVATE", "draft"?: true}→201conuid,slug,version,url. Si elchecksumno cambia, respondeskipped:truesin crear versión. - Estructura mínima:
{"format": "lumbre-lab/1.0", "meta": {"title": "…", "level": "secundaria", "version": "1.0.0", "relations": [{"type": "oa", "uid": "…", "relation": "practices", "activity": "id-de-actividad"}]}, "activities": [{"id": "paso-a-paso", "title": "…", "instructions": "…", "components": [{"component": "math-steps", "params": {…}}]}]}. Losidde actividad son estables: el progreso del alumno se guarda por esa referencia y sobrevive a las actualizaciones. - Relaciones con objetos:
meta.relations[]enlaza el lab con OA u otros labs (el objeto muestra «Explóralo en el laboratorio» con deep link?activity=).
Límites: 6 MB por petición, assets base64 sólo bajo assets/…. El contenido es datos inertes: ningún componente ejecuta JS arbitrario. Flujo recomendado: labs/schema → construir → labs/validate → POST /api/v1/labs con draft:true → previsualizar → re-POSTear sin draft.
Conexión MCP · para ChatGPT, Claude y otros agentes IA
Además de la API REST, Lumbre ofrece un servidor MCP (Model Context Protocol) para que los agentes se conecten de forma nativa mediante herramientas. El agente llama a funciones con nombres claros, recibe respuestas estructuradas y no necesita construir peticiones HTTP a mano.
- Endpoint:
https://filosofia.lumbre.lat/mcp - Protocolo: MCP · Streamable HTTP · JSON-RPC 2.0. El servidor negocia la versión: admite
2025-06-18,2025-03-26y2024-11-05y confirma la que pide el cliente. Respuestas JSON estándar (sin SSE); servidor stateless (sinMcp-Session-Id). Soportainitialize,notifications/initialized,ping,tools/listytools/call. - Health check:
GET /mcp/health→{status, service, version, protocol_versions, transport}(público, sin datos sensibles). - Autenticación: cabecera
Authorization: Bearer <mcp_token>. El token MCP se crea en el Estudio de objetos, en la sección «Tokens MCP» (o por consola conphp bin/mcp-token.php create --all). Es independiente de la clave de API REST.
Herramientas disponibles
lumbre_get_capabilities— descubrir versiones, tipos de bloque, materias, límites y reglas del servidor (sin scope).lumbre_search— buscar categorías, cursos, rutas, objetos y materias en el catálogo.lumbre_list_objects— listar con paginación los objetos de la cuenta (page,limit,query; el personal puede usarall).lumbre_get_object— leer UN objeto completo poridoslug(documento{format, meta, blocks}, versión, estado, visibilidad, assets ycover;include_assetsdevuelve también el base64 de cada recurso ycover_b64).lumbre_get_block_schema— obtener el JSON Schema de un tipo de bloque (campos, ejemplo válido).lumbre_validate_object— validar un documento completo sin publicarlo (devuelvevalid,errorsywarningsconpath/message/solution).lumbre_create_object— crear y publicar un objeto de aprendizaje nuevo (devuelveid,slug,url,version,visibilityycover; con el argumentocoverle das su propia portada).lumbre_update_object— actualizar un objeto existente poridoslug(idempotente: crea una versión nueva, no duplica). La portada actual se conserva salvo que mandescover.lumbre_upload_asset— subir una imagen o audio en base64 para adjuntarla a un objeto; conis_cover:true(+object_id) esa imagen pasa además a ser la PORTADA de la ficha.lumbre_audit_content— detectar objetos publicados con huecos (sin video, imagen, audio, gráfica, actividad evaluable, descripción, etiquetas o portada); es el punto de partida de una campaña de mantenimiento (scopeobjects:read; el personal audita todo el catálogo conall:true).lumbre_find_video— Lumbre busca y valida en YouTube por el agente (que no tiene acceso): porquerydevuelve candidatos VIVOS re-validados por oembed, o verifica unvideo_idconcreto. Nunca inventes ids (scopeobjects:read).lumbre_patch_object— edición parcial de un objeto publicado sin reescribir el documento entero: aplicaops(add_video, add_block, replace_block, remove_block, set_meta, add_image, set_cover) sobre el payload actual, revalida y republica una versión nueva conservando los assets y la portada. Concover(yopsvacío) solo se cambia la portada.dry_run:trueprevisualiza sin publicar (scopeobjects:update).lumbre_list_classrooms— lista las aulas (grupos académicos,kind=group) que el docente gestiona o a las que pertenece, con sujoin_code(sólo si lo administra), nº de miembros, materias y escuela. Punto de partida para saber dónde matricular (scopecourses:read).lumbre_create_classroom— crea un aula privada conjoin_codede 6 caracteres, propiedad del docente; enlaza rutas comosubjectsy la adscribe a una escuela (organization_id/school) que la cuenta gestione. Devuelveid,slug,join_codeeinvite_url. Mismo servicio que la web (scopecourses:write).lumbre_set_subjects— sincroniza las materias (rutas) de un aula: pasas la lista completa deseada (ids o slugs) y hace diff (añade las nuevas, quita las omitidas). Sólo el docente que lo administra (scopecourses:write).lumbre_enroll_students— matricula alumnos en un aula que el docente gestiona. Dos modos:students[]con{identifier}(cuenta existente) o{name}(crea cuenta provisional y devuelvetemp_passworduna vez), ocsv(“nombre,usuario,correo”, ≤500) para carga masiva. Idempotente. Mismo servicio que web y REST (scoperoster:write).lumbre_list_schools— lista las escuelas (organizations) a las que la cuenta pertenece o administra, con su rol y cuántos círculos agrupa. Entrada a la administración escuela↔cursos (scopeschools:read).lumbre_school_courses— lista los cursos y aulas de una escuela (organization): cada círculo con sukind, nº de miembros y url, para vigilancia de matrícula a nivel escuela. La cuenta debe ser miembro de la escuela o personal (scopeschools:read).lumbre_graph_neighbors— Rutas Vivas: devuelve TODAS las aristas tipadas de un objeto (ambas direcciones), cada una con su pregunta y motivo: es exactamente lo que la página del objeto muestra bajo «Rutas vivas». Llámala antes de tejer para no duplicar (scoperelations:read). Ver Rutas vivas.lumbre_graph_gaps— Rutas Vivas: halla objetos publicados huérfanos o con pocas conexiones (max_degree,subject,query,limit). Es el punto de entrada del loop autónomo de tejido: gaps → neighbors → weave → repetir (scoperelations:read).lumbre_weave_edges— Rutas Vivas: teje un lote (hasta 100) de aristas OA↔OA con{from, to, relation, question, note}(ids o slugs). Valida que ambos estén publicados, rechaza autorreferencias y verbos inválidos, y es idempotente (re-tejer actualiza pregunta/motivo). Condry_run:truevalida sin persistir (scoperelations:write).lumbre_unweave_edge— desteje UNA arista (matched exacto defrom+to+relation) para deshacer un tejido erróneo; devuelve cuántas filas borró (scoperelations:write).lumbre_expand_frontier— Rutas Vivas · tela de araña desde una semilla: recibeseed(texto «el sol», id o slug) ytopics[]candidatos; resuelve la semilla por palabra completa (descarta falsos positivos por subcadena), muestra sus vecinos actuales y clasifica cada tema enlink/review-then-create/create. Incluyelimits(3 anillos · 50 nuevos) y elnextdel bucle. Es la brújula del modo «teje desde X» (scoperelations:read).lumbre_analyze_group— Cura de grupos · análisis: audita un curso, categoría (con sus hijos) o ruta ENTEROS de una sola vez. Por cada objeto publicado reporta sus huecos (sin video, imagen, audio, gráfica, actividad evaluable, descripción, etiquetas o portada) y concheck_media:truela salud de sus medios: videos re-validados por oembed (broken_videos), recursosassets/…ausentes en disco (missing_assets) y portada rota (broken_cover). Devuelve resumen por aspecto y lista paginada con la llamada exacta para arreglar cada uno. Es el punto de partida de una campaña masiva (scopeobjects:read). Ver Cura de grupos.lumbre_apply_group— Cura de grupos · ajuste masivo: aplica una tanda de ops (las mismas delumbre_patch_object) a TODO el objeto del grupo que pase el filtro, en una sola llamada. Selecciona conmatch.missing(objetos con ese hueco),match.slugso sin match = todo el grupo;exclude_slugsresta concretos.dry_run:trueprevisualiza (empieza siempre así) ydry_run:falsepublica una versión nueva por objeto. Los objetos se procesan uno a uno y aislados (un fallo no aborta la tanda); los no administrables se omiten (skipped_permission). NO aceptaassets(scopeobjects:update).lumbre_validate_image— Lumbre valida una IMAGEN por el agente (el mismo principio delumbre_find_video, aplicado a fotos): consrc(URL pública o data-URI) la descarga y confirma que es una imagen real y viva — mime detectado de los bytes, formato, dimensiones, peso y aptitud como portada. Atrapa páginas de error disfrazadas y links muertos antes de usarla. Segundo modo:object+src=assets/nombre.pngcomprueba que un recurso ya publicado existe en disco (scopeobjects:read).lumbre_manage_structure— organiza la estructura del catálogo por MCP (gemelo de la API REST/api/v1/structure): una sola herramienta elegida poraction—create_category,create_course(opcionalmente bajo una categoría),create_route(colección, opcionalmente asignada a un curso y con etapas),add_item(coloca un objeto publicado o un libro READY en una ruta/etapa),assign_route(engancha una ruta existente a un curso). Idempotente por slug y condry_run:truepara previsualizar sin crear nada (scopecourses:write).
Cada token MCP tiene scopes (permisos granulares): taxonomy:read, objects:read, objects:create, objects:update, assets:create, schemas:read, courses:read, courses:write, roster:write, schools:read, schools:write, relations:read (inspeccionar el grafo de Rutas Vivas), relations:write (tejer y destejer aristas). Asigna solo los que el agente necesite. El servidor MCP no permite borrar objetos ni tocar usuarios, facturación o configuración del servidor.
Docente ↔ curso (courses:*, roster:write): el agente gestiona el aula como lo haría el profesor en la web — descubre aulas, crea el aula con sus materias, y matricular alumnos uno a uno o en lote, reutilizando el mismo servicio y las mismas reglas. Escuela ↔ cursos (schools:read, schools:write): la administración ve las escuelas de la cuenta y los cursos/aulas de cada una para supervisar la matrícula en lote y adscribir nuevas aulas. Tejido de Rutas Vivas (relations:read, relations:write): el agente inspecciona el grafo, halla huérfanos y teje aristas tipadas con pregunta — el loop autónomo de Rutas vivas.
Cómo configurar MCP en ChatGPT o Claude
En la configuración de herramientas del agente, añade un servidor MCP con estos datos:
URL: https://filosofia.lumbre.lat/mcp Tipo: Streamable HTTP Auth: Bearer <tu_token_mcp>
El agente hará initialize automáticamente, recibirá la lista de herramientas y podrá llamarlas. Para empezar: lumbre_get_capabilities (no requiere scope) y lumbre_search (scope taxonomy:read) son buenas primeras llamadas.
Ejemplos de uso (JSON-RPC 2.0)
Todas las llamadas son POST /mcp con Content-Type: application/json y Authorization: Bearer <token>. El cuerpo sigue el formato JSON-RPC 2.0.
1 · Descubrir capacidades (sin auth):
// Request
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_get_capabilities","arguments":{}},"id":1}
// Response → format, block_types, subjects, limits…
2 · Buscar antes de crear (scope taxonomy:read):
// Request
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_search","arguments":{"entity_type":"object","query":"fotosíntesis"}},"id":2}
// Response → [{id, title, slug, url, state, visibility}…]
3 · Validar sin publicar (scope objects:read):
// Request
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_validate_object","arguments":{"object":{"format":"lumbre-lo/1.0","meta":{"title":"…","author":"…","subject":"Ciencias naturales","min_age":14,"max_age":16,"language":"es"},"blocks":[…]}}},"id":3}
// Response → {valid:true, errors:[], warnings:[]}
4 · Publicar (scope objects:create):
// Request
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_create_object","arguments":{"object":{…},"visibility":"UNLISTED","assets":{"assets/diagrama.png":"<base64>"},"cover":"<base64 de la portada>"}},"id":4}
// Response → {success:true, id:123, slug:"…", url:"https://filosofia.lumbre.lat/object/…", cover:"https://filosofia.lumbre.lat/media/object/123", cover_source:"bundle"}
5 · Tejer Rutas Vivas en lote (scope relations:write; ver la guía):
// Request — dry_run primero; si trae ok:true, reenvía sin dry_run
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_weave_edges","arguments":{"dry_run":true,"edges":[
{"from":"primer-empleo-c-omo-conseguirlo-y-no-morir-en-el-intento","to":"cvl-ind-2-007-tramites-documentos","relation":"requires","question":"¿qué documentos necesito antes de firmar mi primer contrato?","note":"sin RFC e identificación no hay alta en el empleo"},
{"from":5716,"to":5714,"relation":"next","question":"¿cómo protejo mis datos y mi nueva cuenta de banco?","note":"la seguridad digital es el siguiente riesgo al ganar tu primer sueldo"}
]}},"id":8}
// Response → {ok:true, woven:0, skipped:0, would_write:[…], invalid:[]} (dry_run)
// Response → {ok:true, woven:2, skipped:0, invalid:[]} (tejiendo de verdad)
6 · Tejer DESDE una semilla (tela de araña) (scope relations:read): resuelve «el sol» por palabra completa y clasifica cada tema candidato. Con eso, el agente enlaza los que existen y crea (con portada Wikimedia + video verificado + bloque «Fuentes») los que faltan, hasta 3 anillos y 50 objetos nuevos.
// Request
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_expand_frontier","arguments":{"seed":"el sol","topics":["planetas","luz","fotosíntesis","gravedad","eclipse","energía solar","sistema solar"]}},"id":9}
// Response → {success:true, seed:{id:5327, slug:"…", title:"…", url:"…"}, seed_confidence:"40 (palabra completa)", seed_degree:0, branches:[{topic:"planetas", exists:true, action:"link", match:{…}}, {topic:"eclipse", exists:false, action:"review-then-create", weak_match:{…}}, {topic:"energía solar", exists:false, action:"create"}], limits:{max_depth_rings:3, max_new_objects_per_run:50}, next:"BUCLE…"}
Leer y actualizar un objeto existente (JSON-RPC 2.0)
Listar los objetos de la cuenta (scope objects:read):
// Request
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_list_objects","arguments":{"page":1,"limit":20,"query":"fotosíntesis"}},"id":5}
// Response → {objects:[{id, slug, title, state, visibility, url}…], total, page, limit, has_more}
Leer uno completo por id o slug (scope objects:read):
// Request
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_get_object","arguments":{"slug":"mi-objeto","include_assets":true}},"id":6}
// Response → {id, slug, version, state, visibility, object:{format,meta,blocks}, assets, assets_b64}
Actualizar (scope objects:update): edita el object leído y envía lumbre_update_object con el mismo slug (o id). Crea una versión nueva de la misma ficha (no duplica). Si el objeto tiene imágenes, reenvía assets (el base64 de assets_b64) para no perderlas.
// Request
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"lumbre_update_object","arguments":{"slug":"mi-objeto","object":{…editado…}}},"id":7}
// Response → {success:true, id:123, slug:"mi-objeto", version:2, url:"…"}
Autodiagnóstico de la conexión MCP
El proyecto incluye un cliente de diagnóstico que comprueba el flujo real initialize → tools/list → tools/call y, con --crud, el ciclo CREATE → READ → UPDATE → READ:
php bin/mcp-token.php create --all # obtén un token php bin/mcp-test.php --url=https://filosofia.lumbre.lat/mcp --token=<token> --crud
Imprime [OK]/[FAIL] por cada paso y lista las herramientas. Las pruebas automáticas viven en tests/mcp.php (se ejecutan con php tests/run.php).
OAuth 2.1 · conexión automática para ChatGPT y agentes compatibles
El servidor MCP de Lumbre admite OAuth 2.1 con PKCE además de los tokens MCP manuales. Los clientes que soportan OAuth (como ChatGPT) descubren el flujo automáticamente: al recibir un 401 con la cabecera WWW-Authenticate, consultan los metadatos del servidor, se registran, abren el navegador para que el usuario inicie sesión y conceda permisos, y obtienen un access token de 1 hora. No hay que configurar nada manualmente.
- Descubrimiento:
GET /.well-known/oauth-protected-resourceyGET /.well-known/oauth-authorization-server— metadatos públicos con todos los endpoints. - Registro dinámico:
POST /oauth/register— el cliente se registra solo con su nombre yredirect_uris. Solo se admiten clientes públicos (token_endpoint_auth_method: none). - Autorización:
GET /oauth/authorize— el usuario ve una pantalla de consentimiento con los permisos solicitados y pulsa «Autorizar» o «Cancelar». PKCE S256 es obligatorio. - Token:
POST /oauth/token— intercambia el código de autorización por un access token Bearer (1 hora de vida). El código es de un solo uso y dura 2 minutos.
Los tokens MCP manuales (los que se crean en el Estudio) siguen funcionando exactamente igual. OAuth es una vía adicional para clientes que lo soportan; no reemplaza ni cambia los tokens existentes.
Flujo OAuth paso a paso (para desarrolladores)
- El agente envía
POST /mcpsin token → recibe401conWWW-Authenticate: Bearer resource_metadata="…". - Consulta
/.well-known/oauth-authorization-serverpara descubrir endpoints. - Se registra con
POST /oauth/register(envíaclient_nameyredirect_uris). - Genera un
code_verifieraleatorio y calculacode_challenge = SHA-256(code_verifier). - Abre el navegador en
/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=…&state=…&code_challenge=…&code_challenge_method=S256. - El usuario inicia sesión (si no la tiene) y pulsa «Autorizar» en la pantalla de consentimiento.
- El navegador redirige a
redirect_uri?code=…&state=…. - El agente intercambia el código:
POST /oauth/tokencongrant_type=authorization_code,code,redirect_uri,client_idycode_verifier. - Recibe
{"access_token": "…", "token_type": "Bearer", "expires_in": 3600}y lo usa enAuthorization: Bearerpara llamadas MCP.
El formato lumbre-lo/1.0 · los 41 bloques
Materias sugeridas: Ciencias naturales, Matemáticas, Historia, Geografía, Lengua y comunicación, Cívica y ética, Arte, Tecnología. El contenido es dato inerte: nada de HTML; sólo **negrita** y *cursiva*. Todo bloque con respuesta correcta (mcq, true_false, fill_blank, match, sort, sequence, numeric_answer, error_detective, confidence, prediction, text_highlight) lleva evidence: la frase de la propia lección que la justifica.
Catálogo de bloques
heading— { text (≤180), level?: 1|2|3 }text— { text (≤5000); separa párrafos con una línea en blanco }list— { items: [texto,…] (1–30), ordered?: true|false }callout— { text (≤1500), tone?: info|tip|warning|important, title? (≤120) }quote— { text (≤1500), cite? (≤200) }code— { language: uno de python|javascript|typescript|php|sql|html|css|json|java|cpp|csharp|bash|markdown|texto, code (≤8000 chars, ≤120 líneas), title? (≤180), filename? (≤120), highlight_lines?: [enteros 1–120], caption? (≤500) }image— { src "assets/…", alt (≤300), caption? , width?: 1–5 }figure— { src "assets/…", alt (≤300), caption (obligatorio) }audio— { src "assets/…", caption? }video— { src "assets/…", poster? "assets/…", caption? }embed— { provider: "youtube"|"vimeo", id (4–60 chars: el código tras "v=" o "youtu.be/"), title?, caption? } (video embebido SIN subir archivos; la plataforma arma el reproductor)link— { url http(s)…, label (≤180), note? }table— { columns: [texto,…] (1–12), rows: [[celda,…],…] (mismas celdas que columnas), caption? }chart— { chart bar|hbar|line|area|pie|donut|radar, labels: [texto,…], series: [{ name?, values: [número,…] }] (1–8), title?, unit?, x_label?, y_label?, source? } (varía los tipos: hbar para rangos/longitudes, donut/radar para composición, no siempre barras)mindmap— { center: { label (≤48) } (OBLIGATORIO), branches: [{ label (≤48), children?: [{ label, children?: [...] }] },…] (2–9 ramas, máx 3 niveles, ≤60 nodos) } (mapa mental radial; ideal para resumir un tema — el árbol también se lista en texto para quien no usa JS)graph— { curves: [{ expr, label? },…] (1–4) (OBLIGATORIO), x_range?: [min,max], y_range?: [min,max] (tramo ≤10000), title?, x_label?, y_label? } (plano cartesiano: expr con x, números, + - * / ^ ( ) y funciones sin cos tan sqrt abs log ln exp floor round, más la constante pi; p. ej. "sin(x)*x", "x^2-4", "2^x")formula— { math: árbol inerte (OBLIGATORIO), title?, caption?, display?: true|false } (matemáticas en MathML nativo, sin librerías). Nodos: "cadena" (solo glifos, sin llaves/ángulos/comillas), {row:[…]}, {frac:{num,den}}, {sub:{base,idx}}, {sup:{base,exp}}, {sqrt:…}, {op:{name: sum|prod|int|lim|coprod|bigcup|bigcap, from?, to?, body}}, {txt:"palabra"}. Máx 200 nodos / profundidad 8. Ej. raíz cuadrática: {row:["x = ",{frac:{num:{row:["-b ± ",{sqrt:{row:["b",{sup:{base:"b",exp:"2"}},"-4ac"]}}]},"den":"2a"}}]}mcq— { prompt (≤600), options: [texto,…] (2–6), correct_index (base 0), explanation?, evidence (OBLIGATORIO), level? }true_false— { prompt (≤600), answer: true|false, explanation?, evidence (OBLIGATORIO), level? }fill_blank— { text con huecos ___, answers: [texto,…] (# == huecos), explanation?, evidence (OBLIGATORIO), level? }match— { prompt?, pairs: [{left,right},…] (2–12), evidence (OBLIGATORIO), level? }sequence— { prompt?, items: [{id,text},…] (2–20), correct_order: [id,…] (permutación exacta de los id de items), explanation?, evidence (OBLIGATORIO), level? } (ordenar: colocar los elementos en su secuencia correcta)numeric_answer— { prompt (≤600), answer (número), tolerance? (número ≥0: margen absoluto), tolerance_percent? (número ≥0: % sobre answer), unit? (≤24), unit_optional?: true|false, min?/max? (números que acotan la entrada), explanation?, evidence (OBLIGATORIO), level? } (respuesta numérica con tolerancia y unidad opcional)error_detective— { prompt?, lines: [{text},…] (2–30), answer: índice o [índices] (las líneas erróneas, base 0), explanation?, evidence (OBLIGATORIO), level? } (detectar la línea o el paso con error)confidence— { prompt (≤600), options: [texto,…] (2–6), correct_index (base 0), explanation?, evidence (OBLIGATORIO), level? } (opción múltiple + el alumno declara su confianza en 4 niveles)prediction— { prompt (≤600), options: [texto,…] (2–6), correct_index (base 0: el resultado real), reveal: { text? (≤2000), image? ("assets/…"), explanation? }, reflection? (≤600), evidence (OBLIGATORIO), level? } (el alumno predice qué pasará y luego se revela el resultado real)text_highlight— { prompt?, text (≤5000), unit: "word"|"sentence", answer: [índices] o [{category, matches:[índices]}] (base 0 sobre las unidades de text), categories?: [nombres] (2–5; obliga a answer por grupos), evidence (OBLIGATORIO), level? } (el alumno toca palabras o frases para marcarlas)wordsearch— { title?, instructions?, grid: ["MATES",…filas de MAYÚSCULAS sin tildes, 4–12 filas del mismo largo 4–15] (OBLIGATORIO), words: [texto,…] (3–14, ≤40; CADA palabra debe estar trazada en el grid en línea recta) } (sopa de letras de repaso)memory— { title?, instructions?, pairs: [{a,b},…] (3–8; a y b ≤120, ninguna cara repetida) } (juego de memoria: empareja volteando tarjetas)sort— { prompt?, categories: [texto,…] (2–5, ≤80), items: [{text,category},…] (4–14; category idéntica a un nombre de categories), evidence (OBLIGATORIO), level? } (clasificar: arrastrar/toque cada elemento a su categoría)flashcards— { title?, cards: [{front,back},…] (2–60) (la cara back puede llevar **negrita**) }timeline— { title?, events: [{label,title,text, evidence?},…] (2–60) }branch— { title?, nodes: [{id, text, is_end?, choices?: [{label, goto}]},…] (grafo cerrado desde "start") }reflection— { prompt (≤1000), hint?, level? } (pregunta abierta; el alumno escribe)step_reveal— { title? (≤180), steps: [{ title? (≤180), text (≤2000), code? (≤2000), formula? (árbol inerte como en formula), image? ("assets/…"), callout? (≤500) },…] (2–30) } (revelado progresivo: el alumno avanza paso a paso con «Mostrar siguiente»)checkpoint— { title? (≤180), passing_score (número 0–1: umbral), covers?: [id de bloque,…] (por omisión, todos los puntuados del objeto), on_pass? (slug o URL), on_fail? (slug o URL), pass_message? (≤500), fail_message? (≤500) } (agrega los resultados locales de los bloques cubiertos y decide superado / requiere refuerzo)source_compare— { title?, prompt?, sources: [{ kind: text|image|table|link, label?, y según kind: text | src+alt | columns+rows | url },…] (2–4), caption? } (paneles lado a lado que se apilan en móvil; no puntúa: emparéjalo con un mcq o reflection)argument_builder— { title?, prompt?, counterargument?: true|false (añade los pasos contraargumento y respuesta), hints?: { tesis?, evidencia?, razonamiento?, conclusion?, contraargumento?, respuesta? } } (andamio de escritura: tesis → evidencia → razonamiento → conclusión; se guarda como reflexión)before_after— { title?, before_src ("assets/…"), after_src ("assets/…"), before_label? (≤80), after_label? (≤80), alt (≤300), caption? (≤500) } (dos imágenes con un divisor deslizable; táctil y por teclado)scratchpad— { title?, placeholder? (≤500), rows?: entero 2–40, save?: true|false (por omisión true) } (área de texto libre; se autoguarda en el dispositivo y, con sesión y save, en la cuenta)visual_explanation— { title? (≤160), canvas?: wide|square|tall, scenes: [ { caption? (≤160), narration? (≤280), layout?: center|row|column|grid|groups|compare|split|equation, items?: [ {kind: text|number|emoji|shape|image|label|group, id?, value|glyph|shape|src+alt|of, count? 1–24, size? 1–6, color?: accent|warm|ok|ink|line} ], actions?: [ {do: show|hide|highlight|move|pulse|wait, target?: id, delay_ms? 0–8000} ], ask? (≤200) } ] (2–14 escenas: una sola escena no es una secuencia) }
Ejemplo mínimo publicable
{
"object": {
"format": "lumbre-lo/1.0",
"meta": {
"title": "Los estados del agua",
"author": "Roadwise Consulting",
"objective": "Identificar los tres estados del agua y los cambios de estado.",
"subject": "Ciencias naturales",
"min_age": 8,
"max_age": 10,
"language": "es",
"tags": [
"agua",
"estados"
]
},
"blocks": [
{
"type": "heading",
"text": "¿El agua puede caminar?",
"level": 1
},
{
"type": "text",
"text": "El agua vive **tres estados**: sólida (hielo), líquida (agua) y gaseosa (vapor). El calor es lo que la hace cambiar."
},
{
"type": "embed",
"provider": "youtube",
"id": "mtGgo68VM54",
"title": "Estados del agua",
"caption": "Video de repaso."
},
{
"type": "mindmap",
"title": "El mapa del agua",
"center": {
"label": "Agua"
},
"branches": [
{
"label": "Sólida",
"children": [
{
"label": "Hielo"
},
{
"label": "Nieve"
}
]
},
{
"label": "Líquida",
"children": [
{
"label": "Ríos"
},
{
"label": "Lluvia"
}
]
},
{
"label": "Gaseosa",
"children": [
{
"label": "Vapor"
}
]
}
]
},
{
"type": "wordsearch",
"title": "Encuentra los estados",
"instructions": "Hallar: HIELO, VAPOR, AGUA",
"grid": [
"HIELOX",
"QTBMSD",
"VAPORX",
"WNFGHZ",
"AGUAXX",
"JKLCBP"
],
"words": [
"hielo",
"vapor",
"agua"
]
},
{
"type": "sort",
"prompt": "Clasifica cada ejemplo",
"categories": [
"Solido",
"Liquido",
"Gas"
],
"items": [
{
"text": "Cubo de hielo",
"category": "Solido"
},
{
"text": "Lluvia",
"category": "Liquido"
},
{
"text": "Vapor de la olla",
"category": "Gas"
},
{
"text": "Nieve",
"category": "Solido"
}
],
"evidence": "El agua vive tres estados: sólida (hielo), líquida (agua) y gaseosa (vapor)."
},
{
"type": "reflection",
"prompt": "¿Qué cambio de estado ves en tu casa cada día?",
"level": "aplicacion"
}
]
},
"visibility": "PUBLIC"
}
Cómo pedir la clave con curl (para tu propio script)
curl -X POST https://filosofia.lumbre.lat/api/v1/objects \ -H "Authorization: Bearer TU_CLAVE" \ -H "Content-Type: application/json" \ -d @leccion.json
Límites: cuerpo hasta 12 MB · 90 peticiones por hora por clave (los reintentos de corrección cuentan) · cada asset hasta 15 MB (png/jpg/gif/webp/svg/mp3/ogg/wav/webm/mp4). La visibilidad PUBLIC requiere que la cuenta dueña de la clave tenga permiso de publicación; si no, el objeto queda UNLISTED y la respuesta lo avisa.