跳至主要内容
搜索Ctrl+K
博客
LeoChat: pasado y presente de un cliente de agentes

| 35 分钟阅读 |

La verdad es que llamarlo LeoChat dejó de ser exacto hace mucho. ¿LeoMCP? ¿LeoAgent? ¿LeoHarness? Toca todas esas capacidades, pero ningún nombre le queda del todo bien, así que nunca lo cambié.

Código abierto: cybermanhao/LeoChat (licencia MIT)

Cuando arrancó su proyecto predecesor, los agentes de programación acababan de aparecer (y como era difícil comprarlos desde China, yo todavía no había probado ninguno). Hoy, sus funciones ya las cubren de sobra los agentes de programación (¿quién que haga un cliente de agentes no queda cubierto? ╮(╯▽╰)╭). No es que no sirva; es que a una sola persona le resulta muy difícil hacerlo mejor que un equipo lleno de gente de élite.

Cuando se publicó DeepSeek Harness como código abierto, decidí dejar de iterar en este proyecto.

En cuanto a nivel de acabado, LeoChat está lejísimos de dsh, y si en el futuro hago algo parecido, probablemente parta de un proyecto maduro como dsh y lo modifique. Pero sigue siendo la obra de la que me siento más orgulloso.

Este artículo trata de qué es y de qué me dio.

Si quieres probar LeoChat, descarga la última versión desde leo-releases, la página de descargas de este sitio (Windows, descomprimir y usar). Si necesitas una API key para probar, escríbeme a cybermanhao@gmail.com.


Antes de LeoChat

En mayo de 2025 estaba buscando videos sobre RAG en Bilibili cuando, de pronto, me apareció uno en la página de inicio. Su autor era un autor de Zhihu al que seguía desde hacía medio año: Jinhui (锦恢).

El video está aquí: Presentación de OpenMCP.

Cabe mencionar que conseguí la primera reproducción, lo que demuestra lo grande que era mi ansiedad por la IA en ese momento (jaja).

Conseguir la primera reproducción del video de presentación de OpenMCP

El video presentaba OpenMCP, un depurador de MCP bastante mejor que el MCP Inspector. Me emocioné muchísimo: era la primera vez que oía hablar del concepto de MCP, y me resultó absolutamente fascinante. También fue la primera vez que supe que se podía conectar una IA a una aplicación con tanta facilidad. Enseguida armé una base de conocimiento RAG de Yu-Gi-Oh! y la conecté a OpenMCP mediante MCP.

Base de conocimiento RAG de Yu-Gi-Oh! conectada a OpenMCP mediante MCP (1)Base de conocimiento RAG de Yu-Gi-Oh! conectada a OpenMCP mediante MCP (2)

La empresa donde trabajaba justo estaba desarrollando un sistema de atención al cliente con IA, y propuse un enfoque con MCP + RAG: un servicio independiente en Node.js, con otros compañeros encargados del front end y de la integración con el sistema antiguo, y con MCP Servers que se iban agregando para consultas de datos y de texto (gracias a la empresa por dejarme probarlo).

El proyecto salió bien. Fuera de esa tarea, cada vez me fascinaba más OpenMCP. Quería leer su código y hacer algo parecido por mi cuenta.

Yo sabía que OpenMCP era un depurador, pero el simple hecho de “conectarse a MCP y llamar herramientas” ya me parecía impactante.

Solo mucho después me calmé y pensé en su verdadero significado (¿trayectorias de agentes? ¿retorno de datos? ¿rueda de datos? Todavía no sabemos qué pensaba Jinhui en marzo de 2025).

Con esa conmoción, sentí que tenía que construir mi propio cliente de inmediato. Así que escribí a mano, al estilo antiguo, el predecesor de LeoChat: zz-ai-chat.

zz-ai-chat, el predecesor de LeoChat

Pero en la segunda mitad de 2025 empecé a usar poco a poco Cursor, Copilot y Claude Code para iterar de forma automática. Además, zz-ai-chat, por querer lucirme, tenía cuatro configuraciones de build para distintos destinos, y el proyecto se volvió difícil de mantener. Tras una breve pausa (hablaré de eso en otro artículo), comencé la reescritura.

Empezar de nuevo: ¿qué quería?

Al reescribir, me fijé unos objetivos muy acotados:

  1. Fluido y confiable: las llamadas a herramientas de varias rondas y la salida en streaming tenían que ser fluidas, la respuesta rápida y el manejo de errores completo.
  2. Explorar cómo los LLM cambian la interacción: un LLM no debería limitarse a producir texto y llamar herramientas; yo quería además que se encargara de la interacción con la interfaz.
  3. Electron primero, los demás modos después: dudé mucho entre priorizar la web o Electron. La web es donde brilla el SaaS, pero no podía renunciar a las capacidades locales que da Electron (para lograr lo mismo en la web habría que elegir, más o menos, entre servicios en la nube tipo GCP y sandboxes; algún día escribiré sobre eso), así que al final Electron fue el objetivo principal.

Qué puede hacer LeoChat

Es, ante todo, un chatbox con MCP (que fue el planteamiento original del proyecto y hoy ya no parece lo bastante vistoso), y puede conectarse con flexibilidad a todo tipo de herramientas MCP generales.

Los MCP Servers integrados incluyen: sistema de archivos (Filesystem), memoria en grafo de conocimiento (Memory), obtención de páginas web (Fetch), lectura y escritura de Excel, lectura y escritura de Word, automatización del navegador (Playwright) y Everything, para pruebas.

Pero le añadí algo especial, y eso es lo que hace que LeoChat sea distinto de otros clientes.

Controlar la interfaz con IA

Cambiar el color del tema, incluso redimensionar la ventana: un efecto que abre los ojos (literalmente):

La IA también puede renderizar tarjetas: por ejemplo una tarjeta de presentación, una tarjeta de producto o incluso un componente interactivo con botones:

Ejemplo de tarjeta renderizada por IA (1)Ejemplo de tarjeta renderizada por IA (2)Ejemplo de tarjeta renderizada por IA (3)Ejemplo de tarjeta renderizada por IA (4)

Aquí no usé stream-ui, de código abierto de Vercel (ya hablaré de eso); implementé el mío.

Por dentro: las dos cosas exclusivas de LeoChat

El chat, la salida en streaming y las llamadas a herramientas existen también en otros clientes. En lo que de verdad puse empeño es en las dos cosas que se mostraron arriba: los comandos de UI y el sistema de tarjetas.

Comandos de UI: que el modelo estire la mano y “presione” botones

Los efectos de cambiar el tema y redimensionar la ventana se apoyan en una convención muy sencilla.

El leochat-mcp integrado no se diferencia de cualquier otro MCP Server: el modelo lo usa mediante llamadas estándar a herramientas. La diferencia es que sus herramientas no leen archivos ni consultan datos, sino que devuelven un pequeño fragmento de JSON:

{
"__ui_command__": true,
"command": "update_theme",
"payload": { "themeId": "dark-purple" }
}

Cuando el front end ve __ui_command__ en un resultado, busca en el registro de comandos el manejador con el mismo nombre y lo ejecuta.

Actualmente hay 11 comandos registrados:

  • update_theme, resize_window: cambiar el color del tema y redimensionar la ventana de Electron;
  • show_notification, show_confirm: mostrar una notificación o un cuadro de confirmación;
  • open_panel, close_panel: abrir o cerrar paneles laterales;
  • set_input, scroll_to_message, copy_to_clipboard, open_url: editar el campo de entrada, saltar a un mensaje, copiar, abrir un enlace;
  • send_message: enviar un mensaje en nombre del usuario (vuelve a aparecer con las tarjetas).

La ventaja es que ninguno de los dos lados depende del otro: el MCP Server no necesita saber cómo es el front end, y el front end no se preocupa de qué modelo emitió el comando. Para añadir una capacidad nueva basta con agregar un nombre a la tabla de tipos y escribir un manejador.

Eso sí, me puse una regla: leochat-mcp no puede convertirse en una interfaz omnipotente para controlar a distancia todo el front end.

Me imagino cómo acaba ese camino: los límites se vuelven cada vez más difusos, el protocolo se va llenando de cosas, cada comando es un contrato frágil y al final nadie puede mantenerlo. Por eso solo conserva pequeñas acciones “a nivel de host”; todo lo que haya que mostrar como contenido o que pida al usuario elegir se deja a las tarjetas.

El sistema de tarjetas: LeoCard

Al principio, las tarjetas de LeoChat se hacían analizando etiquetas de texto (<card />) en la salida del modelo.

Enseguida vi que ese camino no funcionaba: si el modelo se equivoca en un solo paréntesis, la tarjeta se rompe; y cuando se pulsa un botón, solo se puede adivinar la intención a partir de un formato de texto convenido.

Así que rediseñé un protocolo de tarjetas estructurado, con un solo principio: un comando no es contenido.

  • ui-command se encarga de las “acciones”: cambiar de tema, abrir un panel; son efectos secundarios puntuales;
  • card se encarga del “contenido”: un resumen de producto, el progreso de un render, una elección entre dos opciones para el usuario; cosas que se dibujan en el historial del chat.

La estructura de una tarjeta es pequeña y fácil de componer:

{
"id": "card_1",
"kind": "summary",
"title": "Audífonos inalámbricos",
"tone": "info",
"body": [
{ "type": "image", "image": { "url": "https://…/earbuds.jpg" } },
{ "type": "fields", "fields": [{ "label": "Precio", "value": "$29" }] },
{ "type": "badge", "badge": { "text": "En stock", "tone": "success" } }
],
"actions": [
{
"id": "buy",
"label": "Agregar al carrito",
"kind": "primary",
"action": { "type": "ui-command", "command": "send_message",
"payload": { "text": "Agrega esto a mi carrito" } }
}
]
}
  • kind decide el estilo general: summary, media, list, status, review, error. El campo es abierto: las herramientas externas pueden inventar sus propios nombres, y los kind desconocidos vuelven a un estilo de resumen normal;
  • body es un conjunto de bloques: texto (con Markdown), pares clave-valor, una imagen, varias imágenes, barra de progreso, insignia, separador;
  • tone tiene cinco valores: default, info, success, warning, error;
  • actions son botones. Un clic se despacha a uno de tres destinos: ui-command (ejecutar un comando del host), agent-event (emitir un evento a la aplicación anfitriona), link (abrir un enlace). Las acciones peligrosas pueden llevar confirm, que pide confirmación antes de que el clic surta efecto.

El flujo de datos también es directo: la herramienta devuelve un resultado, el front end analiza el JSON, extrae el sobre de resultado de MCP, lo normaliza a una LeoCard y se lo entrega al renderizador como un elemento de contenido del mensaje.

No hace falta ningún canal de transporte nuevo: cualquier MCP Server, integrado o de terceros, puede dibujar una tarjeta en el chat con solo devolver este formato.

Cerrar el ciclo entre tarjetas y conversación

Lo realmente divertido de las tarjetas es que un botón puede usar el comando de UI send_message para enviarle un mensaje al modelo en nombre del usuario.

Así se forma un ciclo cerrado:

el usuario pregunta → el modelo llama una herramienta → la herramienta devuelve una tarjeta
→ la tarjeta muestra botones → el usuario hace clic
→ send_message envía la intención al modelo → el modelo llama la siguiente herramienta → …

Con esto hice dos Servers de demostración independientes, que también están en el repositorio:

  • pokemon-quiz: un minijuego de adivinar Pokémon. Cada pregunta es una tarjeta con una imagen y cuatro botones de opción; al hacer clic en una opción se envía la respuesta, y “Siguiente” continúa.
  • ecommerce: un flujo de compra completo. Buscar productos, ver detalles, añadir al carrito, hacer el pedido, pagar, seguir el envío; cada paso es una tarjeta, y los botones impulsan el flujo hacia adelante.

Al hacer pokemon-quiz me topé con un problema curioso: en cuanto se mostraba la tarjeta, el modelo no podía evitar “responderse a sí mismo”, analizando las opciones o incluso diciendo la respuesta.

La solución fue añadir al resultado de la herramienta una instrucción solo para el modelo (llmInstruction): “Muestra la tarjeta de arriba tal cual, no analices las opciones, deja que el usuario haga clic en un botón para responder”.

Eso también me hizo entender que, en la UI generativa, la interfaz es para las personas y las instrucciones son para el modelo, y hay que diseñarlas por separado.

El generate_waifu integrado es el ejemplo de tarjeta más simple: devuelve una tarjeta media con unas cuantas imágenes generadas al azar.

MCP integrados: una caja de herramientas lista para usar

El paso más desalentador de cualquier cliente MCP es obligar al usuario a instalar Node, instalar Python y editar JSON por su cuenta.

Por eso, al iniciarse, LeoChat se conecta automáticamente a un conjunto de MCP Servers integrados, y la versión empaquetada incluso se ocupa de las rutas de los runtimes, así que en Windows basta con descomprimir y usar:

ServerQué hace
LeoChat (propio)Control de la interfaz: cambiar tema, redimensionar la ventana, mostrar notificaciones/confirmaciones, abrir y cerrar paneles, editar el campo de entrada, copiar, abrir enlaces; además una herramienta bash para ejecutar comandos (los peligrosos como rm, format y shutdown se rechazan de plano), y generate_waifu, que genera imágenes de anime y las devuelve como tarjeta
FilesystemLeer y escribir archivos locales, listar directorios, buscar archivos
MemoryMemoria a largo plazo en grafo de conocimiento, que recuerda entidades y relaciones entre conversaciones
FetchObtener contenido web y convertirlo en texto fácil de leer para el modelo
ExcelLeer y escribir hojas de cálculo de Excel (en Windows se empaqueta como ejecutable, sin necesidad de Python)
WordLeer y escribir documentos de Word
PlaywrightAutomatización del navegador: abrir páginas, hacer clic, llenar formularios, tomar capturas
EverythingLa implementación de referencia oficial de MCP, usada para probar las características del protocolo

Combinadas, una sola frase puede ejecutar un pequeño flujo de trabajo, por ejemplo “obtén esta página, pasa las tablas a Excel y guárdalo en el escritorio”.

Además de las integradas, se puede añadir cualquier Server de terceros en la página de configuración de MCP, con transporte Stdio o Streamable HTTP.

La versión legal es esta misma base con un MCP adicional de base de conocimiento jurídico.

El resto de la infraestructura, en breve

Son partes inevitables de construir un cliente de agentes confiable, y donde más tiempo invertí, pero no son exclusivas de LeoChat, así que solo unas palabras:

  • TaskLoop: el archivo más grande del sistema. Llama al modelo ronda tras ronda, ejecuta herramientas y devuelve los resultados. Hay varios detalles que resolver por cuenta propia: los argumentos de las llamadas a herramientas en streaming hay que recomponerlos en un JSON completo según su index; una tarea se ejecuta como máximo 50 rondas para evitar bucles infinitos; cualquier resultado de herramienta de más de 3000 caracteres se trunca; y se guarda un punto de control al final de cada ronda, al pausar o al fallar, para poder reanudar.
  • Capa de adaptación de modelos: admite OpenAI, OpenRouter, Anthropic, DeepSeek, Kimi, Gemini y cualquier endpoint compatible. Las URL por defecto, las cabeceras de autenticación y los formatos de streaming son distintos; la capa de adaptación esconde las diferencias y expone una interfaz unificada. Hoy ya existen soluciones comunitarias maduras para esta capa, pero escribirla yo mismo me hizo entender mucho mejor cada parámetro de la API de OpenAI, lo que al depurar sirve más que cualquier documentación.
  • Capa de confiabilidad: reintentos con retroceso exponencial más full jitter, un timeout independiente por intento y un interruptor automático que se “abre” tras fallos consecutivos. Es la misma idea que usé después para integrar plataformas externas en neo-studio: los sistemas externos van a fallar, así que hay que diseñar pensando en el fallo.

El destino de un proyecto no depende solo del esfuerzo personal, sino también del curso de la historia

Lo primero que me hizo sentir perdido fue OpenClaw. Captó la atención de los usuarios no técnicos a la mayor velocidad, y su foco estaba en la parte más engorrosa del trabajo: el gateway.

Siendo sincero, como aplicación de chat independiente LeoChat hoy no tiene ningún valor comercial. Agentes generales como Claude Code y Codex ya están muy maduros, mucho más pulidos de lo que una sola persona puede construir.

Pero me dejó unas cuantas cosas:

  1. El asistente de IA de este blog: haz clic aquí para conversar con él, o usa la página “Acerca de” de arriba (en el móvil, abre el menú con el botón de tres barras). Escribí ese asistente en tres días de ratos libres, y es algo así como mi reemplazo. Pude terminarlo en tres días porque antes había dedicado dos meses a LeoChat, aclarando el bucle del agente, las llamadas a herramientas y la interfaz basada en tarjetas. P.D.: el asistente de IA del blog también puede dejarte con “los ojos abiertos” — pruébalo.
  2. Una comprensión de la capa de protocolo: los transportes de MCP, el ensamblado en streaming de las llamadas a herramientas, los puntos de control y la recuperación; cosas que solo aprendí de verdad al construirlas una vez; leer la documentación no lo reemplaza.
  3. Un conjunto de hábitos para tratar con sistemas externos poco confiables: adaptadores, reintentos, interruptores automáticos, diseñar pensando en el fallo. Cuando después construí la capa de integración de neo-studio, usé casi exactamente el mismo enfoque.
  4. La versión legal: una rama de LeoChat que usa RAG para construir una base de conocimiento sobre derecho chino, y que actualmente usa un profesional del derecho para redactar documentos. Si quieres probarla, también puedes escribirme por correo.

Para terminar

Este es el proyecto personal más complejo y completo que he hecho hasta ahora (a septiembre de 2026), y lo hice únicamente por la pasión de “llevar la IA a las aplicaciones”. En el proceso fui entendiendo poco a poco el significado de OpenMCP e incluso empecé a estudiar infraestructura de IA; con la ayuda de la comunidad de código abierto, mi visión técnica avanzó a gran velocidad.

Creo que la pregunta con la que se topó LeoChat —¿qué sentido tiene un cliente de agentes?— la encontrará todo desarrollador de agentes. Sobre todo cuando intentas usar tu propio agente en el día a día, es inevitable preguntarte: ¿por qué no uso directamente Claude Code? Pero la experiencia de construir un cliente de agentes, un runtime de agentes, es la parte más imprescindible de mi trayectoria técnica hasta ahora.

LeoChat no es un punto final, sino un punto de partida.