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
- Decisiones de diseño
- Requisitos
- Configuración
- Configuración
- Registro
- Ejecutar
- Pruébalo
- Uso en VS Code
- Desplegar
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 unpromptmás opcionalmentecontext,role,system,temperatureymaxTokens. - Una herramienta
quorumenví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
quorumcubre el caso ad hoc de múltiples modelos, y cada ajuste preestablecido enconfig/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 unconfig/presets/refine.jsonse 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.jsonsin 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 oneconfig/models/<name>.jsonjunto 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:
| Transporte | La configuración se relee | Efecto de editar un archivo de configuración |
|---|---|---|
http | por solicitud | En vivo en la siguiente llamada, sin reinicio. |
stdio | una vez por conexión | Tiene 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 enDEFAULT_BASE_URL/DEFAULT_API_KEY.apiKeypuede referenciar una variable de entorno con el prefijoenv:—"apiKey": "env:GPT_KEY"leeGPT_KEYdel 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 rechazantemperature.reasoning—"minimal" | "low" | "medium" | "high"opcional, enviado comoreasoning.effort. Los modelos de razonamiento pueden pasar unmaxTokenscompleto 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íamin(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:
| Clave | Tipo | Predeterminado | Descripción |
|---|---|---|---|
description | string | string[] | requerido | Descripción MCP para la herramienta del ajuste preestablecido. |
roles | PresetRole[] | requerido | Roles aceptados por el ajuste preestablecido. |
mode | "sequential" | "parallel" | "private" | "independent" | "sequential" | Controla qué turnos anteriores ve cada orador de la ronda. |
defaultRounds | entero positivo | 1 | Rondas utilizadas cuando la llamada omite rounds. |
synthesizer | string | ninguno | Rol 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. |
eliminator | string | ninguno | Rol neutral que emite eliminaciones. Requerido con eliminateEvery. |
framer | string | ninguno | Rol 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. |
closingStatements | boolean | false | Ejecuta una fase de cierre antes de la síntesis final. |
eliminateEvery | entero no negativo | "spread" | 0 | Permite al eliminador quitar un orador cada N rondas, o repartir los cortes uniformemente. Solo ajuste preestablecido. |
eliminationsOptional | boolean | false | Permite al eliminador rechazar una eliminación. Solo ajuste preestablecido. |
enterEvery | entero no negativo | 0 | Comienza con un orador por equipo, luego añade un orador en la banca cada N rondas. Solo ajuste preestablecido. |
vote | string | ninguno | Instrucciones 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. |
allowSelfVote | boolean | true | Incluye el propio asiento de cada votante en su menú de candidatos. |
voteByTeam | boolean | false | Presenta una opción por @team y agrega votos por equipo. |
Claves del objeto de rol:
| Clave | Tipo | Predeterminado | Descripción |
|---|---|---|---|
role | string | requerido | Nombre del rol utilizado en los selectores model:role. |
description | string | string[] | archivo de rol coincidente | Instrucciones en línea; los arreglos se unen con saltos de línea. De lo contrario, config/roles/<role>.md debe existir. |
min | entero no negativo | 1 | Mínimo de oradores; 0 hace que el rol sea opcional. |
max | entero positivo o null | 1 | Máximo de oradores; null es ilimitado. |
maxTokens | entero positivo | 8192 | Lí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. |
silent | boolean | false | Permite que el rol observe y vote sin hablar en rondas normales. |
voter | boolean | todos los roles elegibles | Restringe las boletas anónimas a roles marcados cuando cualquier rol está marcado. |
candidate | boolean | todos los roles elegibles | Restringe las opciones de boleta a roles marcados cuando cualquier rol está marcado. |
closing | boolean | todos los roles elegibles | Restringe los cierres a roles marcados; solo el primer orador marcado por equipo o rol sin equipo cierra. |
closingLast | boolean | false | Ejecuta este cerrador después de los cierres paralelos con sus declaraciones en contexto; requiere closing: true. |
tagTeam | boolean | false | Rota un orador marcado por @team en cada ronda normal. No se puede combinar con enterEvery. |
cameo | boolean | false | Habla 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 permitenKEEP 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: trueystructuredContent.incompletecon las etiquetas restantes. Los concursos de eliminación opcional no están sujetos a este requisito. min/maxcuentan a cada orador en ese rol, no por equipo. Acotan el rol completo en todos los equipos, por lo que un rol dewrestlerde equipo de etiquetas que debe cubrir lados desde un 2v2 hasta un 6v6 esmin: 4, max: 12— un 3v3 es solo una dotación válida (6 luchadores) dentro de ese rango, no su propiomax: 3. El motor no puede imponer "lados pares" o "uno por lado" — dilo en eldescriptionen su lugar.- Los roles neutrales no pueden ser en equipo. El
eliminator,framer, y unsynthesizerde 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:
synthesizeEverynecesita unsynthesizer,eliminateEverynecesita uneliminator,reframeEverynecesita unframer,closingLastnecesitaclosing, las opciones de voto necesitanvote, y un sintetizador de un solo asiento necesita al menos otro rol requerido (se queda fuera de las rondas normales).enterEveryytagTeamno 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 × roundsllamadas de modelo seriales, por lo quedefaultRoundsy un generosomaxse 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
incumbentopcional defiende un historial; unelectoratesilencioso 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
@teames 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. Unrun_inopcional golpea el ring por una sola ronda (resérvalo concameoRound), unannounceropcional narra el combate, y elrefnombra al equipo ganador. tiebreak- Una tercera voz decisiva resuelve un punto muerto.
war_games- Un combate en jaula de entrada escalonada: combatientes etiquetados con
@teamentran uno a la vez mientras un árbitro neutral señala faltas y nombra al equipo ganador, con unbookeropcional 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
maxTokenspara un límite duro por turno, y presupuesta generosamente para modelos de razonamiento y quórums de múltiples rondas.tokenBudgetes un eje diferente: cuenta la entrada y la salida de cada llamada para toda la ejecución, así que dimensiónalo desdeturns × (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 herramientaquorumy (solo modo asíncrono) las herramientaspoll/cancel. Elimina una para volver a su cadena integrada. Las herramientas de modelo se describen a sí mismas desde eldescriptionde 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 endescription.mden{longRuns}.config/schema.json— descripciones de campos de entrada (prompt= campos compartidos,quorum= solo quórum; una clavequorumgana 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
| Variable | Obligatorio | Descripción |
|---|---|---|
DEFAULT_BASE_URL | no* | 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_KEY | no* | Clave de API para modelos sin apiKey. Permanece en el lado del servidor. |
ALLOW_NO_MODELS | no | true 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_TRANSPORT | no | http (por defecto) o stdio. |
HOST | no | Dirección de enlace HTTP (por defecto 127.0.0.1). Establezca 0.0.0.0 para exponer — luego establezca ALLOWED_HOSTS. |
ALLOWED_HOSTS | no | Nombres de host separados por comas para protección contra reenlace de DNS en enlaces no locales. |
PORT | no | Puerto HTTP (por defecto 5000; ignorado por stdio). |
MAX_ROUNDS | no | Rondas máximas de discusión que la herramienta quorum acepta (por defecto 5). |
MAX_TOKENS | no | Má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_TIMEOUT | no | Tiempo 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_BUDGET | no | Presupuesto de tokens acumulativo blando por defecto para una ejecución de quorum (sin establecer = sin límite; tokenBudget por llamada lo anula). |
DYNAMIC_ROLES | no | Permitir que la IA llamante defina roles quorum ad hoc en línea (por defecto true). |
PRESETS | no | Lista 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_LEVEL | no | debug | info | warn | error (por defecto info). |
ASYNC_TOOLS | no | true 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_AUTH | no | true 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_MS | no | Lí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: trueañ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 comocancelledsin 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 losHEALTHCHECKs 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 (503si 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.