Legion MCP

Consejos de LLM nativos de MCP para debates, jurados, paneles ciegos, votaciones, refinamiento y deliberación multimodelo altamente personalizable.

Documentación

Legion

"Yo soy Legión, porque somos muchos."

Un MCP-consejo de modelos nativo. Legion expone los LLMs como herramientas individuales y los orquesta en debates, jurados, paneles ciegos, pruebas de refinamiento privadas, talleres y deliberaciones personalizadas con múltiples modelos.

Cada modelo se alcanza a través del formato de cable de la API de Respuestas de OpenAI. Usa OpenAI o Azure directamente, enruta otros proveedores a través de una puerta de enlace compatible (como un proxy LiteLLM), y configura todo el consejo a través de archivos con recarga en caliente.

Contenido

Cómo funciona

flowchart LR
   AI[Calling AI] -->|claude / gpt / gemini …| Legion
   Legion -->|Responses API| GPT[OpenAI / Azure — direct]
   Legion -->|Responses API| GW[Gateway e.g. LiteLLM]
   GW --> Claude & Gemini & Llama
  • Una herramienta por modelo, nombrada según el nombre del modelo en formato slug (p. ej. Claude → claude). Cada una acepta un prompt más opcionalmente context, role, system, temperature y maxTokens.
  • Una herramienta quorum envía un solo prompt a varios modelos — con roles, discusión de múltiples rondas, modos de visibilidad y síntesis — y devuelve cada respuesta por separado. Consulta Ajustes preestablecidos para las opciones de orquestación.
  • Los ajustes preestablecidos son consejos nombrados y preasignados (debate, sala de justicia, revisión de código, …), cada uno expuesto como su propia herramienta.
  • La identidad y la telemetría viajan en structuredContent, no en el texto de la respuesta. El registro se envía a stderr (seguro para stdio).

Decisiones de diseño

  • Sin adaptadores de proveedor. No hay código específico de proveedor ni lista de modelos integrada. Legion habla un solo formato de cable; los modelos que no lo hablan nativamente pasan por una puerta de enlace. Soportar un nuevo modelo no requiere cambios aquí.
  • Los modelos son configuración, no código. Añadir un modelo significa añadir un archivo JSON. Por HTTP el directorio se relee por solicitud, por lo que no hay reconstrucción ni reinicio; por stdio la configuración se lee una vez por conexión (consulta Recarga en caliente).
  • Una herramienta por modelo. Cada modelo aparece ante la IA que llama como su propia herramienta con su propia descripción, en lugar de una sola herramienta con un parámetro de modelo. La herramienta quorum cubre el caso ad hoc de múltiples modelos, y cada ajuste preestablecido en config/presets/ se expone como su propia herramienta de consejo preasignada y aplicada.
  • Sin estado. Cada llamada es de un solo disparo con store: false. Nada se persiste, por lo que no hay base de datos ni estado de conversación que gestionar.
  • Pequeño. Un par de miles de líneas de TypeScript, un archivo de salida empaquetado, cinco dependencias.

Requisitos

  • Node.js 24+
  • Al menos un endpoint compatible con OpenAI-Responses (una API de proveedor directamente, o una puerta de enlace como LiteLLM para modelos que necesitan puente)

Configuración

Desde npm — sin clonar, sin compilar:

npx legion-mcp

Desde un clon:

npm install
copy .env.example .env   # then edit .env

Configuración

Toda la configuración vive en un directorio config/. Los valores predeterminados incluidos son siempre la capa base; una carpeta config/ en el directorio de trabajo actual se superpone sobre ellos, por archivo:

  • Recursos de directorio (models/, roles/, presets/, tools/): un archivo local anula el archivo incluido del mismo nombre; un archivo solo local se añade; cada archivo incluido que no toques permanece. Así que al colocar un config/presets/refine.json se anula solo ese ajuste preestablecido — los demás ajustes preestablecidos incluidos permanecen.
  • Texto de archivo único (prompts.json, errors.json, schema.json): fusionado por clave — incluido < local. Un archivo local parcial anula solo las claves que establece.
  • description.md: gana el local completo si está presente, si no, el incluido.

La superposición puede anular o añadir, pero no eliminar una entrada incluida. Para elegir qué ajustes preestablecidos incluidos se registran como herramientas, usa PRESETS (ver más abajo).

¿Instalando desde npm? Debes proporcionar tus propios archivos de modelo. La configuración incluida solo trae archivos de modelo *.example.json sin clave, que el escáner ignora deliberadamente — por lo que el paquete contribuye cero modelos reales. Sin un archivo de modelo real, el servidor falla rápidamente al inicio (No model files found in ...). Drop one config/models/<name>.json junto a donde ejecutas el servidor (ver más abajo) — el resto se basa en los valores predeterminados incluidos.

El diseño a continuación es idéntico en ambos casos.

Recarga en caliente

La configuración se lee fresca cuando se construye una instancia del servidor, nunca se hornea en el momento de la compilación — pero cuándo sucede eso depende del transporte:

TransporteLa configuración se releeEfecto de editar un archivo de configuración
httppor solicitudEn vivo en la siguiente llamada, sin reinicio.
stdiouna vez por conexiónTiene efecto cuando el cliente se reconecta.

HTTP construye un servidor fresco por solicitud, por lo que un modelo, rol, ajuste preestablecido o anulación de texto añadido se recoge en la siguiente llamada. Stdio fija un servidor durante la vida de la conexión, por lo que la misma edición se aplica cuando el cliente reinicia Legion — que para la mayoría de los clientes MCP de escritorio significa recargar el servidor, no reiniciar la máquina.

Donde este README dice que algo está "en vivo en la siguiente llamada", léelo como el comportamiento HTTP; por stdio es la siguiente conexión.

Modelos — config/models/*.json

Se requiere al menos un archivo de modelo — el servidor falla rápidamente sin uno. Cada archivo JSON se convierte en una herramienta, nombrada según el nombre del archivo en formato slug (config/models/fable.json → herramienta fable):

{
   "model": "claude-fable-5",
   "description": "Claude Fable — fast, creative, general purpose.",
   "baseUrl": "https://api.example.com",
   "apiKey": "sk-optional-per-model-key"
}
  • model (requerido) — el id del modelo desplegado al que enruta el endpoint.
  • description — ayuda a la IA que llama a elegir el modelo correcto.
  • system — instrucciones de sistema de referencia opcionales integradas en cada llamada a este modelo.
  • baseUrl / apiKey — opcional; los valores omitidos se basan en DEFAULT_BASE_URL / DEFAULT_API_KEY. apiKey puede referenciar una variable de entorno con el prefijo env: — "apiKey": "env:GPT_KEY" lee GPT_KEY del entorno (no establecido/vacío es un error fatal de inicio), por lo que una clave puede permanecer fuera del archivo por completo.
  • omitParams — lista opcional de parámetros de solicitud a descartar para este modelo, p. ej. ["temperature"]. El servidor permanece agnóstico al proveedor: nunca asume qué modelos rechazan qué parámetros — declaras las peculiaridades de cada modelo aquí. Útil para modelos de razonamiento y algunas implementaciones que rechazan temperature.
  • reasoning — "minimal" | "low" | "medium" | "high" opcional, enviado como reasoning.effort. Los modelos de razonamiento pueden pasar un maxTokens completo pensando y no emitir nada; "low" puede ayudar a dejar espacio para una respuesta. El endpoint debe soportar este parámetro; no elude el filtrado de contenido.
  • maxTokens — límite de salida máximo opcional para este modelo. Cada solicitud envía min(requested, ceiling), sea lo que sea lo que el llamador o el rol del ajuste preestablecido pidió. Para modelos que piensan más cuanto más espacio tienen y vuelven vacíos.

Inserción en caliente: el directorio se reescanea por solicitud — añade o edita un archivo de modelo y estará en vivo en la siguiente llamada, sin reinicio.

Secretos y git: los archivos de modelo pueden contener claves API, por lo que config/models/*.json está ignorado por git. Copia un *.example.json (rastreado, sin clave, ignorado por el escáner) para comenzar:

copy config\models\gpt.example.json config\models\gpt.json   # then add your key

Roles — config/roles/*.md

Archivos de instrucciones opcionales con inserción en caliente. Cada archivo .md se convierte en un rol nombrado (slugificado desde el nombre del archivo). Coloca un archivo, estará en vivo en la siguiente llamada. Este repositorio incluye skeptic.md, builder.md, judge.md y short.md (un rol conciso de "responde inmediatamente, sin deliberación" útil para turnos de salida restringida) como iniciadores listos para usar — edítalos o elimínalos libremente (no contienen secretos).

Los selectores disponibles en las herramientas se convierten en roleName, p. ej. pasar role: "skeptic" o usar "model:skeptic" en quorum.models.

Ajustes preestablecidos — config/presets/*.json

Recetas de consejo opcionales con inserción en caliente, un archivo JSON por ajuste preestablecido (nombrado según el nombre del archivo en formato slug, como los modelos). Cada ajuste preestablecido se convierte en su propia herramienta — coloca config/presets/code_review.json y aparece una herramienta code_review en la siguiente solicitud. Cada ajuste preestablecido tiene un description, una lista roles y valores predeterminados opcionales autoritativos mode / synthesizer. Cada rol define su comportamiento en línea — el description de un rol es sus instrucciones (el contrato de comportamiento); un rol sin description se basa en un archivo config/roles/<role>.md coincidente:

{
   "description": [
      "Free-for-all: pit several contestants against each other, then crown a winner.",
      "",
      "Staff `contestant` with as many models as you like; one `judge` decides."
   ],
   "mode": "parallel",
   "synthesizer": "judge",
   "roles": [
      { "role": "contestant", "description": "Argue why your answer beats the others.", "min": 2, "max": null },
      { "role": "judge",      "description": "Crown a single winner and justify it.", "min": 1, "max": 1 }
   ]
}

La IA que llama invoca la herramienta del ajuste preestablecido directamente (p. ej. code_review) y aún escribe los selectores models, asignando cualquier modelo a cualquier rol del ajuste preestablecido. Los ajustes preestablecidos están aplicados: cada selector debe usar un rol del ajuste preestablecido y cada rol debe estar asignado dentro de su cardinalidad, si no, el resultado es un error que dice qué corregir.

Claves:

ClaveTipoPredeterminadoDescripción
descriptionstring | string[]requeridoDescripción MCP para la herramienta del ajuste preestablecido.
rolesPresetRole[]requeridoRoles aceptados por el ajuste preestablecido.
mode"sequential" | "parallel" | "private" | "independent""sequential"Controla qué turnos anteriores ve cada orador de la ronda.
defaultRoundsentero positivo1Rondas utilizadas cuando la llamada omite rounds.
synthesizerstringningunoRol que produce turnos de síntesis. Un rol de un solo asiento es neutral y se sienta fuera de las rondas; un rol de múltiples asientos (max > 1 o null) juega, y su primer asiento activo sintetiza.
synthesizeEvery"end" | non-negative integer"end"Ejecuta la síntesis al final o cada N rondas.
eliminatorstringningunoRol neutral que emite eliminaciones. Requerido con eliminateEvery.
framerstringningunoRol neutral que abre y redirige la discusión.
reframeEvery"end" | non-negative integer"end"Reencuadra solo en la apertura o cada N rondas después de la apertura.
closingStatementsbooleanfalseEjecuta una fase de cierre antes de la síntesis final.
eliminateEveryentero no negativo | "spread"0Permite al eliminador quitar un orador cada N rondas, o repartir los cortes uniformemente. Solo ajuste preestablecido.
eliminationsOptionalbooleanfalsePermite al eliminador rechazar una eliminación. Solo ajuste preestablecido.
enterEveryentero no negativo0Comienza con un orador por equipo, luego añade un orador en la banca cada N rondas. Solo ajuste preestablecido.
votestringningunoInstrucciones de votación; habilita la votación anónima.
voteEvery"end" | non-negative integer"end"Vota al final o cada N rondas.
voteVisibility"aggregate" | "ballots""aggregate"Incluye solo totales o también opciones de votación anonimizadas en la transcripción.
allowSelfVotebooleantrueIncluye el propio asiento de cada votante en su menú de candidatos.
voteByTeambooleanfalsePresenta una opción por @team y agrega votos por equipo.

Claves del objeto de rol:

ClaveTipoPredeterminadoDescripción
rolestringrequeridoNombre del rol utilizado en los selectores model:role.
descriptionstring | string[]archivo de rol coincidenteInstrucciones en línea; los arreglos se unen con saltos de línea. De lo contrario, config/roles/<role>.md debe existir.
minentero no negativo1Mínimo de oradores; 0 hace que el rol sea opcional.
maxentero positivo o null1Máximo de oradores; null es ilimitado.
maxTokensentero positivo8192Límite de salida para este rol cuando el llamante omite maxTokens. Un valor explícito del llamante anula todos los roles, incluidos los roles neutrales. Incluye razonamiento oculto.
silentbooleanfalsePermite que el rol observe y vote sin hablar en rondas normales.
voterbooleantodos los roles elegiblesRestringe las boletas anónimas a roles marcados cuando cualquier rol está marcado.
candidatebooleantodos los roles elegiblesRestringe las opciones de boleta a roles marcados cuando cualquier rol está marcado.
closingbooleantodos los roles elegiblesRestringe los cierres a roles marcados; solo el primer orador marcado por equipo o rol sin equipo cierra.
closingLastbooleanfalseEjecuta este cerrador después de los cierres paralelos con sus declaraciones en contexto; requiere closing: true.
tagTeambooleanfalseRota un orador marcado por @team en cada ronda normal. No se puede combinar con enterEvery.
cameobooleanfalseHabla en exactamente una ronda — el cameoRound de la llamada, si no, el punto medio — en lugar de cada ronda. Es una irrupción, no un regular.

Por ejemplo, una llamada de tribunal asigna abogados a los lados con etiquetas @team. El primer abogado listado para cada lado da la declaración de cierre de ese lado:

{
   "models": [
      "gpt:lawyer@prosecution",
      "grok:lawyer@prosecution",
      "claude:lawyer@defense",
      "kimi:juror",
      "llama:juror",
      "mistral:juror",
      "opus:judge"
   ],
   "objectives": {
      "prosecution": "Prove liability.",
      "defense": "Defeat liability."
   }
}

Notas de autoría

Cosas que muerden al escribir un preset:

  • Las eliminaciones usan etiquetas exactas. El juez copia un comando CUT <label> del menú elegible, luego da una razón en la siguiente línea. Los cortes opcionales también permiten KEEP ALL. Los números desnudos y las selecciones ambiguas se rechazan. Las anulaciones de indicaciones de eliminación local deben usar este formato de comando.
  • La eliminación requerida puede atascarse. Los reintentos de limpieza acotados no garantizan un único sobreviviente. Una ejecución sin resolver conserva su resumen y discusión, pero devuelve isError: true y structuredContent.incomplete con las etiquetas restantes. Los concursos de eliminación opcional no están sujetos a este requisito.
  • min/max cuentan a cada orador en ese rol, no por equipo. Acotan el rol completo en todos los equipos, por lo que un rol de wrestler de equipo de etiquetas que debe cubrir lados desde un 2v2 hasta un 6v6 es min: 4, max: 12 — un 3v3 es solo una dotación válida (6 luchadores) dentro de ese rango, no su propio max: 3. El motor no puede imponer "lados pares" o "uno por lado" — dilo en el description en su lugar.
  • Los roles neutrales no pueden ser en equipo. El eliminator, framer, y un synthesizer de un solo asiento rechazan una etiqueta @team, por lo que un rol que pertenece a un lado no puede ocupar esos puestos.
  • Algunas claves requieren otras, y una violación se detecta al cargar: synthesizeEvery necesita un synthesizer, eliminateEvery necesita un eliminator, reframeEvery necesita un framer, closingLast necesita closing, las opciones de voto necesitan vote, y un sintetizador de un solo asiento necesita al menos otro rol requerido (se queda fuera de las rondas normales). enterEvery y tagTeam no se pueden combinar — ambos deciden quién habla.
  • Un preset malformado se omite, no es fatal. Registra ❌ preset skipped — Invalid <file>: <reason> y todas las demás herramientas aún se registran, así que revisa el registro del servidor cuando una herramienta de preset no aparezca.
  • El costo es speakers × rounds llamadas de modelo seriales, por lo que defaultRounds y un generoso max se multiplican rápidamente. mode: "parallel" colapsa cada ronda a su orador más lento, al precio de que los oradores ya no se ven entre sí dentro de una ronda.

Este repositorio incluye estos presets — edítalos o bórralos libremente:

battle_royale
Concurso de todos contra todos; un supervisor corona a un ganador.
brainstorm
Generación de ideas divergentes entre modelos.
bullying
Un modelo defiende una posición mientras los demás se amontonan contra él; un maestro opcional decide si se mantuvo.
code_review
Revisión de código estructurada con múltiples modelos.
courtroom
Abogados etiquetados por equipo argumentan lados opuestos, los jurados votan por lado, y un juez falla.
debate
Lados opuestos argumentan una pregunta hacia una síntesis.
double_blind
Panel ciego independiente — nadie ve a los demás.
election
Los candidatos hacen campaña, luego el campo decide por voto secreto — el voto anónimo es el veredicto, no la decisión de un juez. Un incumbent opcional defiende un historial; un electorate silencioso opcional lee cada ronda y vota sin hacer campaña.
final_girl
Sobrevivientes eliminados uno por ronda hasta que quede uno.
focus_group
Panel moderado que improvisa entre sí.
gauntlet
Carrera privada de auto-refinamiento a través de rondas.
quick_take
Verificación rápida de dos voces — una respuesta directa, una línea de contraataque.
refine
Pulido de relevo de un artefacto existente.
tag_team
Combate de equipo de etiquetas: un luchador etiquetado con @team es legal por equipo cada ronda y el resto espera en la esquina, rotando cada ronda para que un lado de tres personas pase por los tres. Un run_in opcional golpea el ring por una sola ronda (resérvalo con cameoRound), un announcer opcional narra el combate, y el ref nombra al equipo ganador.
tiebreak
Una tercera voz decisiva resuelve un punto muerto.
war_games
Un combate en jaula de entrada escalonada: combatientes etiquetados con @team entran uno a la vez mientras un árbitro neutral señala faltas y nombra al equipo ganador, con un booker opcional que establece el combate.
workshop
Equipo creativo diferenciado.

Qué presets agrupados se registran como herramientas está controlado por PRESETS — sin configurar los registra todos, y un preset solo local (un slug sin contraparte agrupada) que agregues bajo tu propio config/presets/ siempre se registra independientemente, ya que crear uno es la aceptación. Un archivo local que reutiliza un slug agrupado es una personalización de ese preset, no uno nuevo, por lo que aún obedece a PRESETS. Sin presets agrupados y sin presets locales no hay herramientas de preset — pero nota que el conjunto agrupado es la capa base, así que obtienes cada preset agrupado incluso cuando no envías una carpeta config/presets/ propia.

El texto del rol empuja la salida, no la limita — usa maxTokens para un límite duro por turno, y presupuesta generosamente para modelos de razonamiento y quórums de múltiples rondas. tokenBudget es un eje diferente: cuenta la entrada y la salida de cada llamada para toda la ejecución, así que dimensiónalo desde turns × (context + maxTokens) y establécelo por encima de la estimación — la entrada domina una vez que crece la transcripción.

Guía de IA — config/description.md

Markdown opcional servido a los clientes como MCP instructions — describe tus modelos y cuándo la IA debería usar cada uno. Consulta la copia de este repositorio para una plantilla. El marcador literal {longRuns} se reemplaza con config/sync/long-runs.md o config/async/long-runs.md dependiendo de ASYNC_TOOLS, por lo que la guía de entrega (bloquear-y-esperar vs iniciar/sondear/cancelar) sigue el modo sin duplicar el resto del archivo. Un description.md local sin el marcador se sirve tal como está escrito; una advertencia de inicio nota que falta la guía de modo.

Texto de herramientas, campos y mensajes — config/*.json y config/tools/*.md

El texto que un llamante lee para conducir Legion — descripciones de herramientas y campos, andamiaje de indicaciones, errores de ejecución — vive en la configuración, no en el código. (Descripciones de forma de resultados en outputSchema, líneas de dotación generadas, y errores de inicio permanecen en el código.) Cada archivo se fusiona sobre la base JSON agrupada por clave, así que anula solo lo que quieras; abre las copias enviadas para ver el conjunto completo de claves y los marcadores de posición {token}:

  • config/tools/quorum.md, poll.md, cancel.md — descripciones para la herramienta quorum y (solo modo asíncrono) las herramientas poll / cancel. Elimina una para volver a su cadena integrada. Las herramientas de modelo se describen a sí mismas desde el description de su archivo de modelo, y las herramientas de preset desde el propio preset.
  • config/sync/long-runs.md, config/async/long-runs.md — el bloque específico del modo insertado en description.md en {longRuns}.
  • config/schema.json — descripciones de campos de entrada (prompt = campos compartidos, quorum = solo quórum; una clave quorum gana en un conflicto de nombres).
  • config/prompts.json — las plantillas de modelado de indicaciones que los modelos leen: contrato de rol, bloque de contexto, encabezado de transcripción, banners de ronda. Ajusta cuán fuertemente se vinculan los roles y cómo se enmarcan las rondas aquí.
  • config/errors.json — mensajes de error de ejecución mostrados a la IA llamante.

(Los errores de inicio/validación de configuración permanecen en el código — un mensaje que reporta un archivo de configuración roto no puede vivir dentro de él.)

Variables de entorno

VariableObligatorioDescripción
DEFAULT_BASE_URLno*Raíz de API para modelos sin baseUrl — el SDK añade /responses. P. ej. https://api.openai.com/v1, https://<res>.openai.azure.com/openai/v1; un proxy LiteLLM funciona en su raíz simple.
DEFAULT_API_KEYno*Clave de API para modelos sin apiKey. Permanece en el lado del servidor.
ALLOW_NO_MODELSnotrue arranca incluso cuando no existen archivos de modelo: cero herramientas de modelo; quorum y las herramientas predefinidas se registran pero fallan al usarse hasta que aparece un config/models/*.json (recargado en caliente por solicitud). Para demostraciones y sandboxes de registro que solo listan herramientas. Por defecto false — los modelos faltantes siguen siendo fatales.
MCP_TRANSPORTnohttp (por defecto) o stdio.
HOSTnoDirección de enlace HTTP (por defecto 127.0.0.1). Establezca 0.0.0.0 para exponer — luego establezca ALLOWED_HOSTS.
ALLOWED_HOSTSnoNombres de host separados por comas para protección contra reenlace de DNS en enlaces no locales.
PORTnoPuerto HTTP (por defecto 5000; ignorado por stdio).
MAX_ROUNDSnoRondas máximas de discusión que la herramienta quorum acepta (por defecto 5).
MAX_TOKENSnoMáximo visible maxTokens por turno que un llamador puede solicitar; valores mayores se rechazan (por defecto 3000). Los límites de rol predefinidos por encima se recortan. Las asignaciones de razonamiento por modelo se añaden encima.
MODEL_TIMEOUTnoTiempo de espera por llamada de modelo en ms (por defecto 90000), para que un asiento atascado no bloquee un consejo. Se reintenta una vez, por lo que el peor caso de un asiento es aproximadamente el doble antes de convertirse en un turno fallido.
TOKEN_BUDGETnoPresupuesto de tokens acumulativo blando por defecto para una ejecución de quorum (sin establecer = sin límite; tokenBudget por llamada lo anula).
DYNAMIC_ROLESnoPermitir que la IA llamante defina roles quorum ad hoc en línea (por defecto true).
PRESETSnoLista blanca separada por comas de slugs predefinidos incluidos para registrar como herramientas (p. ej. code_review,debate). Sin establecer = cada predefinido incluido, por lo que las actualizaciones nunca eliminan uno silenciosamente; establecido = solo estos, por lo que un predefinido recién enviado nunca aparece sin invitación. Los predefinidos que añada bajo su propio config/presets/ se registran siempre — crear uno es la aceptación — mientras que un archivo local que comparte un slug predefinido personaliza ese predefinido y aún obedece la lista. Los nombres desconocidos se ignoran.
LOG_LEVELnodebug | info | warn | error (por defecto info).
ASYNC_TOOLSnotrue ejecuta consejos en segundo plano: quorum y las herramientas predefinidas devuelven un identificador de trabajo inmediatamente y se exponen las herramientas poll / cancel. Para hosts cuyas llamadas de herramienta expiran antes de que un consejo pueda terminar. Los trabajos viven solo en la memoria de este proceso. Por defecto false.
TRUST_PROXY_AUTHnotrue vincula cada trabajo al llamador nombrado por los encabezados X-MS-CLIENT-PRINCIPAL-* (Azure Container Apps Easy Auth). Solo es seguro detrás de una entrada que elimine copias proporcionadas por el cliente. Por defecto false — cualquier titular de un jobId puede consultar o cancelarlo.
JOB_MAX_ACTIVE / JOB_MAX_RETAINED / JOB_RETAIN_MS / JOB_POLL_INTERVAL_MS / JOB_SHUTDOWN_GRACE_MSnoLímites de trabajos asíncronos: consejos concurrentes (3), trabajos terminados conservados (20), retención (1800000 ms), cadencia de consulta sugerida (10000 ms), drenaje SIGTERM (20000 ms).

* Cada modelo debe resolver un baseUrl y apiKey desde su archivo o los valores predeterminados — validado al inicio.

El servidor falla rápidamente al inicio ante un directorio de modelos faltante o vacío (a menos que ALLOW_NO_MODELS=true), archivos de modelo inválidos, un endpoint o clave irresoluble, o dos nombres de archivo que se conviertan en slugs de la misma herramienta.

Enrutamiento

Cada llamada de herramienta es una solicitud de API de Responses sin estado y de un solo disparo. Los modelos cuyos endpoints hablan nativamente Responses (OpenAI, Azure OpenAI / Foundry) establecen un baseUrl para ser llamados directamente; el resto recurre a los valores predeterminados — típicamente una puerta de enlace compatible con OpenAI como LiteLLM que conecta con sus APIs nativas.

Registro

  • info (azul): inicio del servidor y una línea de metadatos por llamada de modelo — modelo, latencia, uso de tokens, rol, presencia de contexto. Sin contenido de prompt/respuesta.
  • debug (gris): además registra el prompt y la respuesta completos (el contexto se indica como presente, no se imprime).
  • warn (naranja) / error (rojo): respaldos y fallos.

El color se desactiva automáticamente cuando stderr no es una TTY.

Ejecución

Un punto de entrada; el transporte proviene de MCP_TRANSPORT (http es el predeterminado, establezca stdio para clientes MCP de escritorio).

Desde npm (bin legion-mcp — ejecute desde un directorio que contenga su config/):

npx legion-mcp                             # Streamable HTTP transport on :$PORT/mcp
$env:MCP_TRANSPORT='stdio'; npx legion-mcp # stdio transport

Instalado globalmente o como dependencia, el mismo binario está en PATH:

npm install -g legion-mcp
legion-mcp

Desarrollo (sin paso de compilación, vía tsx):

npm run dev         # Streamable HTTP transport on :$PORT/mcp
npm run dev:stdio   # stdio transport

Producción (compilado a bin/server.js):

npm run build
npm start           # http
npm run start:stdio # stdio

Modo asíncrono — ASYNC_TOOLS=true

Un consejo puede ejecutarse durante minutos; ChatGPT y Codex alojados abandonan una llamada de herramienta mucho antes (observado ~124 s; Codex predetermina a 60 s). Con ASYNC_TOOLS=true las herramientas quorum y predefinidas validan la solicitud, inician el consejo en segundo plano y devuelven de inmediato un identificador — structuredContent.jobId, el state/phase actual y un pollIntervalMs sugerido. Aparecen dos herramientas adicionales:

  • poll — solo lectura; devuelve el estado del trabajo, las respuestas y notas públicas completadas hasta ahora y (una vez terminal) result: la salida completa del consejo en su forma habitual. full: true añade la transcripción renderizada y la línea de tiempo.
  • cancel — aborta llamadas de modelo en curso, devuelve lo que se haya completado y resuelve el trabajo como cancelled sin síntesis. Idempotente.

Las herramientas de modelo individuales permanecen síncronas. Las herramientas del servidor instructions cambian al flujo de inicio/consulta/cancelación para que el modelo llamante sepa qué hacer. Los trabajos se mantienen en memoria de proceso bajo límites JOB_* — un reinicio los pierde, y una solicitud enrutada a una réplica diferente no puede verlos, así que ejecute una sola instancia (o fije enrutamiento persistente) cuando este modo esté activo. Establezca TRUST_PROXY_AUTH=true detrás de Azure Container Apps Easy Auth para vincular cada trabajo al llamador autenticado; sin ello, la posesión del jobId es el único control de acceso.

Pruébelo

Liste las herramientas con el MCP Inspector:

npx @modelcontextprotocol/inspector -e MCP_TRANSPORT=stdio npx tsx ts/server.ts

Uso en VS Code

Añada a su mcp.json — desde npm:

{
   "servers": {
      "legion": {
         "command": "npx",
         "args": ["-y", "legion-mcp"],
         "cwd": "path/to/your/config/parent",
         "env": {
            "MCP_TRANSPORT": "stdio",
            "DEFAULT_BASE_URL": "https://your-gateway.example.com",
            "DEFAULT_API_KEY": "sk-your-key"
         }
      }
   }
}

O desde un clon:

{
   "servers": {
      "legion": {
         "command": "node",
         "args": ["bin/server.js"],
         "cwd": "path/to/legion",
         "env": {
            "MCP_TRANSPORT": "stdio",
            "DEFAULT_BASE_URL": "https://your-gateway.example.com",
            "DEFAULT_API_KEY": "sk-your-key"
         }
      }
   }
}

Para el transporte HTTP, apunte su cliente a http://<host>:<PORT>/mcp.

Salud

  • GET /health — vitalidad económica: confirma que el proceso está activo y la configuración cargada. Devuelve { status: "ok", name, version, models } (un recuento). No realiza llamadas externas. Esto es lo que los HEALTHCHECKs de contenedor y las sondas de vitalidad/disponibilidad de Kubernetes deberían consultar.
  • GET /health?deep — verificación de conectividad opcional: envía un prompt pequeño a cada modelo e informa la accesibilidad por modelo (503 si alguno falla). Realiza una llamada facturable real por modelo, así que úselo manualmente — no lo conecte a una sonda automática.

Implementación

Ejemplos de implementación de contenedores listos para usar (Azure App Service, Azure Container Apps, Docker Compose, Kubernetes y Compose + Caddy para HTTPS) viven en examples/ — cada uno instala Legion desde npm y envía un config/ completo listo para usar.