Legion MCP
Conselhos de LLM nativos de MCP para debates, júris, painéis cegos, votação, refinamento e deliberação altamente personalizável com múltiplos modelos.
Documentação
Legion
"Eu sou Legion, pois somos muitos."
Um conselho de modelos nativo de MCP. O Legion expõe LLMs como ferramentas individuais e os orquestra em debates, júris, painéis cegos, desafios privados de refinamento, workshops e deliberações personalizadas com múltiplos modelos.
Cada modelo é acessado através do formato de wire da Responses API da OpenAI. Use OpenAI ou Azure diretamente, roteie outros provedores através de um gateway compatível (como um proxy LiteLLM) e configure todo o conselho através de arquivos com recarga a quente.
Conteúdo
- Como funciona
- Decisões de design
- Requisitos
- Configuração
- Configuração
- Registro de logs
- Executar
- Experimente
- Uso no VS Code
- Implantar
Como 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
- Uma ferramenta por modelo, nomeada após o nome do modelo em formato slug (ex.:
Claude→claude). Cada uma aceita umpromptalém decontext,role,system,temperatureemaxTokensopcionais. - Uma ferramenta
quorumdistribui um prompt para vários modelos — com papéis, discussão em múltiplas rodadas, modos de visibilidade e síntese — e retorna cada resposta separadamente. Veja Presets para as opções de orquestração. - Presets são conselhos nomeados e pré-configurados (debate, tribunal, revisão de código, …), cada um exposto como sua própria ferramenta.
- Identidade e telemetria viajam em
structuredContent, não no texto da resposta. O registro de logs vai para stderr (seguro para stdio).
Decisões de design
- Sem adaptadores de provedor. Não há código específico de provedor nem lista de modelos embutida. O Legion fala um único formato de wire; modelos que não o falam nativamente passam por um gateway. Suportar um novo modelo não exige mudanças aqui.
- Modelos são configuração, não código. Adicionar um modelo significa adicionar um arquivo JSON. Via HTTP, o diretório é relido a cada requisição, então não há rebuild ou reinício; via stdio, a configuração é lida uma vez por conexão (veja Recarga a quente).
- Uma ferramenta por modelo. Cada modelo aparece para a IA chamadora como sua própria ferramenta
com sua própria descrição, em vez de uma única ferramenta com um parâmetro de modelo.
A ferramenta
quorumcobre o caso ad-hoc de múltiplos modelos, e cada preset emconfig/presets/é exposto como sua própria ferramenta de conselho pré-configurada e obrigatória. - Sem estado. Cada chamada é única com
store: false. Nada é persistido, então não há banco de dados nem estado de conversa para gerenciar. - Pequeno. Alguns milhares de linhas de TypeScript, um único arquivo de saída empacotado, cinco dependências.
Requisitos
- Node.js 24+
- Pelo menos um endpoint compatível com OpenAI-Responses (uma API de provedor diretamente, ou um gateway como LiteLLM para modelos que precisam de ponte)
Configuração
Do npm — sem clone, sem build:
npx legion-mcp
De um clone:
npm install
copy .env.example .env # then edit .env
Configuração
Toda a configuração vive em um diretório config/. Os padrões empacotados são
sempre a camada base; uma pasta config/ no diretório de trabalho atual
é sobreposta sobre eles, por arquivo:
- Recursos de diretório (
models/,roles/,presets/,tools/): um arquivo local substitui o arquivo empacotado de mesmo nome; um arquivo apenas local é adicionado; todo arquivo empacotado que você não tocar permanece. Então, colocar umconfig/presets/refine.jsonsubstitui apenas aquele preset — os outros presets empacotados permanecem. - Texto de arquivo único (
prompts.json,errors.json,schema.json): mesclado por chave — empacotado < local. Um arquivo local parcial substitui apenas as chaves que define. description.md: local vence inteiro se presente, senão o empacotado.
A sobreposição pode substituir ou adicionar, mas não excluir uma entrada empacotada. Para escolher
quais presets empacotados são registrados como ferramentas, use PRESETS (veja abaixo).
Instalando do npm? Você deve fornecer seus próprios arquivos de modelo. A configuração empacotada inclui apenas arquivos de modelo
*.example.jsonsem chave, que o scanner deliberadamente ignora — então o pacote contribui com zero modelos reais. Sem um arquivo de modelo real, o servidor falha rapidamente na inicialização (No model files found in ...). Drop oneconfig/models/<name>.jsonao lado de onde você executa o servidor (veja abaixo) — o restante recai nos padrões empacotados.
O layout abaixo é idêntico de qualquer forma.
Recarga a quente
A configuração é lida novamente quando uma instância do servidor é construída, nunca gravada em tempo de build — mas quando isso acontece depende do transporte:
| Transporte | A configuração é relida | Efeito de editar um arquivo de configuração |
|---|---|---|
http | por requisição | Ao vivo na próxima chamada, sem reinício. |
stdio | uma vez por conexão | Tem efeito quando o cliente reconecta. |
O HTTP constrói um servidor novo por requisição, então um modelo, papel, preset ou substituição de texto adicionado é captado na próxima chamada. O stdio fixa um servidor para a vida da conexão, então a mesma edição chega quando o cliente reinicia o Legion — o que para a maioria dos clientes MCP de desktop significa recarregar o servidor, não reiniciar a máquina.
Onde este README diz que algo está "ao vivo na próxima chamada", leia como o comportamento HTTP; via stdio é a próxima conexão.
Modelos — config/models/*.json
Pelo menos um arquivo de modelo é obrigatório — o servidor falha rapidamente sem um.
Cada arquivo JSON se torna uma ferramenta, nomeada após o nome do arquivo em formato slug
(config/models/fable.json → ferramenta fable):
{
"model": "claude-fable-5",
"description": "Claude Fable — fast, creative, general purpose.",
"baseUrl": "https://api.example.com",
"apiKey": "sk-optional-per-model-key"
}
model(obrigatório) — o id do modelo implantado para o qual o endpoint roteia.description— ajuda a IA chamadora a escolher o modelo certo.system— instruções de sistema de linha de base opcionais embutidas em cada chamada para este modelo.baseUrl/apiKey— opcionais; valores omitidos recaem emDEFAULT_BASE_URL/DEFAULT_API_KEY.apiKeypode referenciar uma variável de ambiente com o prefixoenv:—"apiKey": "env:GPT_KEY"lêGPT_KEYdo ambiente (não definido/vazio é um erro fatal de inicialização), então uma chave pode ficar fora do arquivo inteiramente.omitParams— lista opcional de parâmetros de requisição a descartar para este modelo, ex.:["temperature"]. O servidor permanece agnóstico de provedor: ele nunca assume quais modelos rejeitam quais parâmetros — você declara as peculiaridades de cada modelo aqui. Útil para modelos de raciocínio e algumas implantações que rejeitamtemperature.reasoning—"minimal" | "low" | "medium" | "high"opcional, enviado comoreasoning.effort. Modelos de raciocínio podem gastar ummaxTokensinteiro pensando e não emitir nada;"low"pode ajudar a deixar espaço para uma resposta. O endpoint deve suportar este parâmetro; ele não contorna a filtragem de conteúdo.maxTokens— teto de saída rígido opcional para este modelo. Cada requisição enviamin(requested, ceiling), seja o que o chamador ou o papel do preset pediu. Para modelos que pensam mais quanto mais espaço têm e voltam vazios.
Adição a quente: o diretório é reescaneado por requisição — adicione ou edite um arquivo de modelo e ele estará ao vivo na próxima chamada, sem reinício.
Segredos e git: arquivos de modelo podem conter chaves de API, então config/models/*.json
é ignorado pelo git. Copie um *.example.json (rastreado, sem chave, ignorado pelo
scanner) para começar:
copy config\models\gpt.example.json config\models\gpt.json # then add your key
Papéis — config/roles/*.md
Arquivos de instrução opcionais com adição a quente. Cada arquivo .md se torna um papel nomeado
(slugificado a partir do nome do arquivo). Coloque um arquivo, ele está ao vivo na próxima chamada. Este repositório
inclui skeptic.md, builder.md, judge.md e short.md (um papel sucinto de "responda
imediatamente, sem deliberação" útil para turnos com saída restrita) como
iniciadores prontos para uso — edite ou exclua livremente (eles não contêm segredos).
Seletores disponíveis nas ferramentas se tornam roleName, ex.: passar role: "skeptic"
ou usar "model:skeptic" em quorum.models.
Presets — config/presets/*.json
Receitas de conselho opcionais com adição a quente, um arquivo JSON por preset (nomeado
após o nome do arquivo em formato slug, como modelos). Cada preset se torna sua própria
ferramenta — coloque config/presets/code_review.json e uma ferramenta code_review aparece
na próxima requisição. Cada preset tem um description, uma lista de roles e
padrões opcionais autoritativos de mode / synthesizer. Cada papel define seu
comportamento inline — o description de um papel é suas instruções (o contrato
de comportamento); um papel sem description recai em um arquivo
config/roles/<role>.md correspondente:
{
"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 }
]
}
A IA chamadora invoca a ferramenta de preset diretamente (ex.: code_review) e ainda
escreve os seletores de models, atribuindo qualquer modelo a qualquer papel do preset. Presets
são obrigatórios: cada seletor deve usar um papel do preset e cada papel deve ser
preenchido dentro de sua cardinalidade, caso contrário o resultado é um erro dizendo o que corrigir.
Chaves:
| Chave | Tipo | Padrão | Descrição |
|---|---|---|---|
description | string | string[] | obrigatório | Descrição MCP para a ferramenta de preset. |
roles | PresetRole[] | obrigatório | Papéis aceitos pelo preset. |
mode | "sequential" | "parallel" | "private" | "independent" | "sequential" | Controla quais turnos anteriores cada orador da rodada vê. |
defaultRounds | inteiro positivo | 1 | Rodadas usadas quando a chamada omite rounds. |
synthesizer | string | nenhum | Papel que produz turnos de síntese. Um papel de assento único é neutro e fica de fora das rodadas; um papel de múltiplos assentos (max > 1 ou null) participa, e seu primeiro assento ativo sintetiza. |
synthesizeEvery | "end" | non-negative integer | "end" | Executa a síntese no final ou a cada N rodadas. |
eliminator | string | nenhum | Papel neutro que emite eliminações. Obrigatório com eliminateEvery. |
framer | string | nenhum | Papel neutro que abre e redireciona a discussão. |
reframeEvery | "end" | non-negative integer | "end" | Reformula apenas na abertura ou a cada N rodadas após a abertura. |
closingStatements | boolean | false | Executa uma fase de encerramento antes da síntese final. |
eliminateEvery | inteiro não negativo | "spread" | 0 | Permite que o eliminador remova um orador a cada N rodadas, ou distribua os cortes uniformemente. Apenas preset. |
eliminationsOptional | boolean | false | Permite que o eliminador recuse uma eliminação. Apenas preset. |
enterEvery | inteiro não negativo | 0 | Começa com um orador por equipe e adiciona um orador reserva a cada N rodadas. Apenas preset. |
vote | string | nenhum | Instruções de votação; habilita votação anônima. |
voteEvery | "end" | non-negative integer | "end" | Vota no final ou a cada N rodadas. |
voteVisibility | "aggregate" | "ballots" | "aggregate" | Inclui apenas totais ou também escolhas de votação anonimizadas no transcript. |
allowSelfVote | boolean | true | Inclui o próprio assento de cada votante em seu menu de candidatos. |
voteByTeam | boolean | false | Apresenta uma escolha por @team e agrega votos por equipe. |
Chaves do objeto de papel:
| Chave | Tipo | Padrão | Descrição |
|---|---|---|---|
role | string | obrigatório | Nome do papel usado nos seletores model:role. |
description | string | string[] | arquivo de papel correspondente | Instruções inline; arrays são unidos com quebras de linha. Caso contrário, config/roles/<role>.md deve existir. |
min | inteiro não negativo | 1 | Mínimo de falantes; 0 torna o papel opcional. |
max | inteiro positivo ou null | 1 | Máximo de falantes; null é ilimitado. |
maxTokens | inteiro positivo | 8192 | Limite de saída para este papel quando o chamador omite maxTokens. Um valor explícito do chamador substitui todos os papéis, incluindo papéis neutros. Inclui raciocínio oculto. |
silent | boolean | false | Permite que o papel observe e vote sem falar nas rodadas normais. |
voter | boolean | todos os papéis elegíveis | Restringe cédulas anônimas a papéis marcados quando qualquer papel está marcado. |
candidate | boolean | todos os papéis elegíveis | Restringe opções de cédula a papéis marcados quando qualquer papel está marcado. |
closing | boolean | todos os papéis elegíveis | Restringe encerramentos a papéis marcados; apenas o primeiro falante marcado por equipe ou papel sem equipe encerra. |
closingLast | boolean | false | Executa este encerrador após encerramentos paralelos com suas declarações em contexto; requer closing: true. |
tagTeam | boolean | false | Rotaciona um falante marcado por @team em cada rodada normal. Não pode ser combinado com enterEvery. |
cameo | boolean | false | Fala em exatamente uma rodada — o cameoRound da chamada, senão o ponto médio — em vez de todas as rodadas. Um intruso, não um regular. |
Por exemplo, uma chamada de tribunal atribui advogados a lados com tags @team. O
primeiro advogado listado para cada lado faz a declaração de encerramento daquele 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 autoria
Coisas que complicam ao escrever um preset:
- Eliminações usam rótulos exatos. O juiz copia um comando
CUT <label>do menu elegível e depois dá um motivo na linha seguinte. Cortes opcionais também permitemKEEP ALL. Números puros e seleções ambíguas são rejeitados. Substituições locais do prompt de eliminação devem usar este formato de comando. - Eliminação obrigatória pode travar. Tentativas limitadas de limpeza não garantem
um único sobrevivente. Uma execução não resolvida mantém seu resumo e discussão, mas
retorna
isError: trueestructuredContent.incompletecom os rótulos restantes. Concursos com eliminação opcional não estão sujeitos a este requisito. min/maxcontam cada falante naquele papel, não por equipe. Eles limitam o papel inteiro em todas as equipes, então um papel de tag teamwrestlerque precisa cobrir lados de um 2v2 até um 6v6 émin: 4, max: 12— um 3v3 é apenas uma escalação válida (6 lutadores) dentro desse intervalo, não seu própriomax: 3. O motor não pode impor "lados pares" ou "um por lado" — diga isso nodescriptionem vez disso.- Papéis neutros não podem ser em equipe. O
eliminator,framere umsynthesizerde assento único rejeitam uma tag@team, então um papel que pertence a um lado não pode ocupar essas funções. - Algumas chaves exigem outras, e uma violação é detectada no carregamento:
synthesizeEveryprecisa de umsynthesizer,eliminateEveryprecisa de umeliminator,reframeEveryprecisa de umframer,closingLastprecisa declosing, opções de votação precisam devotee um sintetizador de assento único precisa de pelo menos um outro papel obrigatório (ele fica de fora das rodadas normais).enterEveryetagTeamnão podem ser combinados — ambos decidem quem fala. - Um preset malformado é ignorado, não fatal. Ele registra
❌ preset skipped — Invalid <file>: <reason>e todas as outras ferramentas ainda são registradas, então verifique o log do servidor quando uma ferramenta de preset não aparecer. - Custo é
speakers × roundschamadas de modelo em série, entãodefaultRoundse ummaxgeneroso multiplicam rapidamente.mode: "parallel"colapsa cada rodada para seu falante mais lento, ao preço de os falantes não se verem mais dentro de uma rodada.
Este repositório inclui estes presets — edite ou exclua livremente:
battle_royale- Concurso de todos contra todos; um supervisor coroa um vencedor.
brainstorm- Geração divergente de ideias entre modelos.
bullying- Um modelo defende uma posição enquanto os demais se unem contra ele; um professor opcional decide se ele se manteve firme.
code_review- Revisão de código estruturada com múltiplos modelos.
courtroom- Advogados com tags de equipe argumentam lados opostos, jurados votam por lado e um juiz decide.
debate- Lados opostos argumentam uma questão para uma síntese.
double_blind- Painel cego independente — ninguém vê os outros.
election- Candidatos fazem campanha, então o grupo decide por voto secreto — o voto anônimo é o veredito, não a decisão de um juiz. Um
incumbentopcional defende um histórico; umelectoratesilencioso opcional lê todas as rodadas e vota sem fazer campanha. final_girl- Sobreviventes eliminados um por rodada até restar um.
focus_group- Painel moderado que reage às ideias uns dos outros.
gauntlet- Corrida privada de auto-refinamento entre rodadas.
quick_take- Verificação rápida de intuição com duas vozes — uma resposta direta, uma linha de contestação.
refine- Polimento em revezamento de um artefato existente.
tag_team- Luta de tag team: um lutador com tag
@teamé elegível por equipe a cada rodada e os demais esperam no córner, rotacionando a cada rodada para que um lado de três pessoas passe por todos os três. Umrun_inopcional entra no ringue por uma única rodada (agende comcameoRound), umannounceropcional narra a luta e orefnomeia a equipe vencedora. tiebreak- Uma terceira voz decisiva resolve um impasse.
war_games- Uma luta em gaiola com entrada escalonada: combatentes com tag
@teamentram um de cada vez enquanto um árbitro neutro marca faltas e nomeia a equipe vencedora, com umbookeropcional que define a luta. workshop- Equipe criativa diferenciada.
Quais presets empacotados são registrados como ferramentas é controlado por
PRESETS — sem definir, registra todos, e um preset apenas local
(um slug sem contraparte empacotada) que você adiciona sob seu próprio
config/presets/ é sempre registrado independentemente, já que criar um é o
opt-in. Um arquivo local que reutiliza um slug empacotado é uma personalização daquele
preset, não um novo, então ainda obedece a PRESETS. Sem presets empacotados e
sem presets locais, não há ferramentas de preset — mas note que o conjunto empacotado é a camada
base, então você recebe todos os presets empacotados mesmo sem enviar uma pasta config/presets/
própria.
O texto do papel orienta a saída, não a limita — use
maxTokenspara um limite rígido por turno e orce generosamente para modelos de raciocínio e quóruns de múltiplas rodadas.tokenBudgeté um eixo diferente: conta a entrada e a saída de cada chamada para toda a execução, então dimensione a partir deturns × (context + maxTokens)e defina acima da estimativa — a entrada domina quando o transcripto cresce.
Orientação de IA — config/description.md
Markdown opcional servido aos clientes como MCP instructions — descreva seus
modelos e quando a IA deve usar cada um. Veja a cópia deste repositório para um modelo.
O marcador literal {longRuns} é substituído por config/sync/long-runs.md ou
config/async/long-runs.md dependendo de ASYNC_TOOLS, então a orientação
de entrega (bloquear-e-esperar vs iniciar/consultar/cancelar) acompanha o modo sem
duplicar o resto do arquivo. Um description.md local sem o marcador
é servido como escrito; um aviso de inicialização observa que a orientação de modo está ausente.
Texto de ferramenta, campo e mensagem — config/*.json e config/tools/*.md
O texto que um chamador lê para dirigir o Legion — descrições de ferramentas e campos, andaimes
de prompt, erros de execução — vive na configuração, não no código. (Descrições de formato
de resultado em outputSchema, linhas de escalação geradas e erros de inicialização
permanecem no código.) Cada arquivo mescla sobre a base JSON empacotada por chave, então
substitua apenas o que quiser; abra as cópias enviadas para ver o conjunto completo de chaves e
espaços reservados {token}:
config/tools/quorum.md,poll.md,cancel.md— descrições para a ferramentaquorume (somente modo assíncrono) as ferramentaspoll/cancel. Exclua uma para voltar à sua string embutida. Ferramentas de modelo se descrevem a partir dodescriptionde seu arquivo de modelo, e ferramentas de preset a partir do próprio preset.config/sync/long-runs.md,config/async/long-runs.md— o bloco específico do modo inserido emdescription.mdem{longRuns}.config/schema.json— descrições de campos de entrada (prompt= campos compartilhados,quorum= somente quórum; uma chavequorumvence em conflito de nome).config/prompts.json— os modelos de formatação de prompt que os modelos leem: contrato de papel, bloco de contexto, cabeçalho de transcripto, banners de rodada. Ajuste aqui o quão fortemente os papéis se vinculam e como as rodadas são enquadradas.config/errors.json— mensagens de erro de execução mostradas à IA chamadora.
(Erros de inicialização/validação de configuração permanecem no código — uma mensagem que relata um arquivo de configuração quebrado não pode viver dentro dele.)
Variáveis de ambiente
| Variável | Obrigatório | Descrição |
|---|---|---|
DEFAULT_BASE_URL | não* | Raiz da API para modelos sem um baseUrl — o SDK acrescenta /responses. Ex.: https://api.openai.com/v1, https://<res>.openai.azure.com/openai/v1; um proxy LiteLLM funciona na sua raiz simples. |
DEFAULT_API_KEY | não* | Chave de API para modelos sem um apiKey. Permanece no lado do servidor. |
ALLOW_NO_MODELS | não | true inicia mesmo quando nenhum arquivo de modelo existe: zero ferramentas de modelo; quorum e ferramentas predefinidas são registradas, mas falham ao serem usadas até que um config/models/*.json apareça (recarregado a quente por solicitação). Para demonstrações e sandboxes de registro que apenas listam ferramentas. Padrão false — modelos ausentes permanecem fatais. |
MCP_TRANSPORT | não | http (padrão) ou stdio. |
HOST | não | Endereço de bind HTTP (padrão 127.0.0.1). Defina 0.0.0.0 para expor — então defina ALLOWED_HOSTS. |
ALLOWED_HOSTS | não | Nomes de host separados por vírgula para proteção contra rebinding de DNS em binds não localhost. |
PORT | não | Porta HTTP (padrão 5000; ignorada por stdio). |
MAX_ROUNDS | não | Máximo de rodadas de discussão que a ferramenta quorum aceita (padrão 5). |
MAX_TOKENS | não | Máximo visível de maxTokens por turno que um chamador pode solicitar; valores maiores são rejeitados (padrão 3000). Limites de função predefinidos acima disso são limitados. Alocações de raciocínio por modelo são adicionadas por cima. |
MODEL_TIMEOUT | não | Tempo limite por chamada de modelo em ms (padrão 90000), para que um assento travado não trave um conselho. Repetido uma vez, então o pior caso de um assento é aproximadamente o dobro antes de se tornar um turno falho. |
TOKEN_BUDGET | não | Orçamento padrão suave cumulativo de tokens para uma execução de quorum (não definido = sem limite; tokenBudget por chamada substitui). |
DYNAMIC_ROLES | não | Permitir que a IA chamadora defina funções quorum ad-hoc inline (padrão true). |
PRESETS | não | Lista de permissões separada por vírgulas de slugs predefinidos empacotados para registrar como ferramentas (ex.: code_review,debate). Não definido = todos os predefinidos empacotados, então atualizações nunca descartam um silenciosamente; definido = apenas estes, então um predefinido empacotado recém-lançado nunca aparece sem convite. Predefinidos que você adiciona sob seu próprio config/presets/ são sempre registrados — criar um é o opt-in — enquanto um arquivo local que compartilha um slug predefinido personaliza esse predefinido e ainda obedece à lista. Nomes desconhecidos são ignorados. |
LOG_LEVEL | não | debug | info | warn | error (padrão info). |
ASYNC_TOOLS | não | true executa conselhos em segundo plano: quorum e ferramentas predefinidas retornam um identificador de trabalho imediatamente e ferramentas poll / cancel são expostas. Para hosts cujas chamadas de ferramenta expiram antes que um conselho termine. Os trabalhos vivem apenas na memória deste processo. Padrão false. |
TRUST_PROXY_AUTH | não | true vincula cada trabalho ao chamador nomeado pelos cabeçalhos X-MS-CLIENT-PRINCIPAL-* (Azure Container Apps Easy Auth). Somente seguro atrás de um ingress que remove cópias fornecidas pelo cliente. Padrão false — qualquer detentor de um jobId pode consultar ou cancelá-lo. |
JOB_MAX_ACTIVE / JOB_MAX_RETAINED / JOB_RETAIN_MS / JOB_POLL_INTERVAL_MS / JOB_SHUTDOWN_GRACE_MS | não | Limites de trabalho assíncrono: conselhos simultâneos (3), trabalhos concluídos mantidos (20), retenção (1800000 ms), cadência de consulta sugerida (10000 ms), dreno SIGTERM (20000 ms). |
* Todo modelo deve resolver um baseUrl e apiKey de seu arquivo ou dos
padrões — validado na inicialização.
O servidor falha rapidamente na inicialização em um diretório de modelos ausente/vazio
(a menos que ALLOW_NO_MODELS=true), arquivos de modelo inválidos, um endpoint ou chave
irresolvível, ou dois nomes de arquivo que se transformam no mesmo slug de ferramenta.
Roteamento
Toda chamada de ferramenta é uma solicitação stateless de API Responses de uso único. Modelos cujos
endpoints falam nativamente Responses (OpenAI, Azure OpenAI / Foundry) definem um
baseUrl para serem chamados diretamente; o restante recorre aos padrões —
tipicamente um gateway compatível com OpenAI como LiteLLM que faz a ponte para suas APIs
nativas.
Registro
info(azul): início do servidor e uma linha de metadados por chamada de modelo — modelo, latência, uso de tokens, função, presença de contexto. Sem conteúdo de prompt/resposta.debug(cinza): adicionalmente registra o prompt e a resposta completos (o contexto é anotado como presente, não impresso).warn(laranja) /error(vermelho): fallbacks e falhas.
A cor é desativada automaticamente quando stderr não é um TTY.
Executar
Um único ponto de entrada; o transporte vem de MCP_TRANSPORT (http é o
padrão, defina stdio para clientes MCP de desktop).
Do npm (bin legion-mcp — execute a partir de um diretório contendo seu config/):
npx legion-mcp # Streamable HTTP transport on :$PORT/mcp
$env:MCP_TRANSPORT='stdio'; npx legion-mcp # stdio transport
Instalado globalmente ou como dependência, o mesmo binário está em PATH:
npm install -g legion-mcp
legion-mcp
Desenvolvimento (sem etapa de build, via tsx):
npm run dev # Streamable HTTP transport on :$PORT/mcp
npm run dev:stdio # stdio transport
Produção (compilado para bin/server.js):
npm run build
npm start # http
npm run start:stdio # stdio
Modo assíncrono — ASYNC_TOOLS=true
Um conselho pode levar minutos; ChatGPT e Codex hospedados abandonam uma chamada de ferramenta muito
antes disso (observado ~124 s; Codex usa padrão de 60 s). Com ASYNC_TOOLS=true,
as ferramentas quorum e predefinidas validam a solicitação, iniciam o conselho em
segundo plano e retornam imediatamente com um identificador — structuredContent.jobId, o
state/phase atual e um pollIntervalMs sugerido. Duas ferramentas extras
aparecem:
poll— somente leitura; retorna o estado do trabalho, as respostas e notas públicas concluídas até agora e (uma vez terminal)result: a saída completa do conselho em seu formato usual.full: trueadiciona a transcrição renderizada e a linha do tempo.cancel— aborta chamadas de modelo em andamento, retorna o que já foi concluído e encerra o trabalho comocancelledsem síntese. Idempotente.
Ferramentas de modelo individuais permanecem síncronas. Ferramentas de servidor instructions mudam para o
fluxo de trabalho iniciar/consultar/cancelar para que o modelo chamador saiba o que fazer. Os trabalhos são mantidos
na memória do processo sob limites JOB_* — um reinício os perde, e uma solicitação
roteada para uma réplica diferente não pode vê-los, então execute uma única instância (ou
fixe roteamento sticky) quando este modo estiver ativo. Defina TRUST_PROXY_AUTH=true atrás de
Azure Container Apps Easy Auth para vincular cada trabalho ao chamador autenticado;
sem isso, a posse do jobId é o único controle de acesso.
Experimente
Liste as ferramentas com o MCP Inspector:
npx @modelcontextprotocol/inspector -e MCP_TRANSPORT=stdio npx tsx ts/server.ts
Uso no VS Code
Adicione ao seu mcp.json — do 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"
}
}
}
}
Ou de um clone:
{
"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 o transporte HTTP, aponte seu cliente para http://<host>:<PORT>/mcp.
Saúde
GET /health— liveness barato: confirma que o processo está ativo e a configuração carregada. Retorna{ status: "ok", name, version, models }(uma contagem). Não faz chamadas externas. É isso que osHEALTHCHECKs de contêiner e as sondas de liveness/readiness do Kubernetes devem acessar.GET /health?deep— verificação opcional de conectividade: envia um prompt minúsculo para cada modelo e relata a acessibilidade por modelo (503se algum falhar). Faz uma chamada real e cobrável por modelo, então use manualmente — não conecte a uma sonda automática.
Implantar
Exemplos prontos de implantação de contêiner (Azure App Service, Azure Container
Apps, Docker Compose, Kubernetes e Compose + Caddy para HTTPS) estão em
examples/ — cada um instala Legion do npm e inclui um
config/ completo de substituição.