Legion

io.github.faulkj由 GitHub 帳號 faulkj 發布v0.8.0更新於 Oct 24, 2026

MCP-native LLM councils for debates, juries, blind panels, refinement, and custom deliberation.

已驗證STDIO僅桌面開發者工具AI 與機器學習

概覽

AI 產生的概覽

Legion 是一個本機 MCP 伺服器,把各個大型語言模型暴露成獨立工具,並將它們編排成辯論、評審團、盲評小組等多模型審議流程。

可以做什麼
呼叫單一模型:每個已設定的模型都是獨立工具,接受 prompt、context、role、system、temperature 和 maxTokens,回傳該模型的回答,身分與遙測資訊放在結構化內容中而非回答正文。執行 quorum:把同一個提示分送給多個模型,支援角色、多輪討論、可見性模式和綜合總結,並分別回傳每個回答。呼叫預設審議:辯論、法庭、程式碼審查、選舉、淬鍊、工作坊等具名且預先配置角色的方案,每個都註冊為獨立且帶驗證的工具。非同步模式下管理長任務:quorum 和預設工具立即回傳任務代號,poll 與 cancel 工具用於查看進度、取得完整結果或中止執行。
適用情境
當你希望多個模型針對同一問題互相討論——對抗式辯論、盲評、程式碼審查、創意發想或反覆打磨——而不是只得到一個模型的回答,並且願意設定模型檔案、為每次執行的多次模型呼叫付費時使用。
執行需求
需要 Node.js 24 以上版本,以 stdio(MCP_TRANSPORT=stdio)或 HTTP 方式在本機執行。至少一個相容 OpenAI Responses 的端點,可直接使用某家服務商,也可經由 LiteLLM 之類的閘道。config/models 下至少需要一個真實模型檔案;npm 套件只附不含金鑰的範例檔,缺少真實檔案時會啟動失敗,除非把 ALLOW_NO_MODELS 設為 true。憑證透過 DEFAULT_API_KEY、DEFAULT_BASE_URL,或模型檔案中以環境變數引用的 apiKey(例如 OPENAI_API_KEY)提供。
安裝前請注意
模型檔案可能包含 API 金鑰,雖已被 git 忽略,仍建議把金鑰放在環境變數中。每次審議都會產生大量序列模型呼叫(發言者數乘以輪數),費用會迅速累積。非同步模式下任務只存在於行程記憶體,重新啟動即遺失;未啟用 TRUST_PROXY_AUTH 時,任何持有 jobId 的人都能輪詢或取消任務。TRUST_PROXY_AUTH 只應部署在會剝除用戶端自帶 X-MS-CLIENT-PRINCIPAL 標頭的入口之後。把 HTTP 傳輸暴露到 localhost 之外需要額外設定主機白名單。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Legion,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

Legion

"I am Legion, for we are many."

An MCP-native model council. Legion exposes LLMs as individual tools and orchestrates them into debates, juries, blind panels, private refinement gauntlets, workshops, and custom multi-model deliberations.

Every model is reached through the OpenAI Responses API wire format. Use OpenAI or Azure directly, route other providers through a compatible gateway (such as a LiteLLM proxy), and configure the entire council through hot-reloadable files.

Contents

How it works

mermaid
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
  • One tool per model, named after the slugified model name (e.g. Claude → claude). Each accepts a prompt plus optional context, role, system, temperature, and maxTokens.
  • A quorum tool fans one prompt out to several models — with roles, multi-round discussion, visibility modes, and synthesis — and returns each answer separately. See Presets for the orchestration options.
  • Presets are named, pre-staffed councils (debate, courtroom, code review, …), each exposed as its own tool.
  • Identity and telemetry ride in structuredContent, not the answer text. Logging goes to stderr (safe for stdio).

Design decisions

  • No provider adapters. There is no provider-specific code and no built-in model list. Legion speaks one wire format; models that don't speak it natively go through a gateway. Supporting a new model requires no change here.
  • Models are config, not code. Adding a model means adding a JSON file. Over HTTP the directory is re-read per request, so no rebuild or restart; over stdio the config is read once per connection (see Hot-reload).
  • One tool per model. Each model appears to the calling AI as its own tool with its own description, rather than a single tool with a model parameter. The quorum tool covers the ad-hoc multi-model case, and each preset in config/presets/ is exposed as its own enforced, pre-staffed council tool.
  • Stateless. Every call is one-shot with store: false. Nothing is persisted, so there is no database and no conversation state to manage.
  • Small. A couple thousand lines of TypeScript, one bundled output file, five dependencies.

Requirements

  • Node.js 24+
  • At least one OpenAI-Responses-compatible endpoint (a provider API directly, or a gateway such as LiteLLM for models that need bridging)

Setup

From npm — no clone, no build:

pwsh
npx legion-mcp

From a clone:

pwsh
npm installcopy .env.example .env   # then edit .env

Configuration

All configuration lives in a config/ directory. The bundled defaults are always the base layer; a config/ folder in the current working directory is overlaid on top of them, per file:

  • Directory resources (models/, roles/, presets/, tools/): a local file overrides the bundled file of the same name; a local-only file is added; every bundled file you don't touch stays. So dropping in one config/presets/refine.json overrides just that preset — the other bundled presets remain.
  • Single-file text (prompts.json, errors.json, schema.json): merged per key — bundled < local. A partial local file overrides only the keys it sets.
  • description.md: local wins whole if present, else bundled.

The overlay can override or add, but not delete a bundled entry. To choose which bundled presets register as tools, use PRESETS (see below).

Installing from npm? You must supply your own model files. The bundled config ships only key-free *.example.json model files, which the scanner deliberately ignores — so the bundle contributes zero real models. With no real model file the server fails fast at startup (No model files found in ...). Drop one config/models/<name>.json next to where you run the server (see below) — the rest falls back to the bundled defaults.

The layout below is identical either way.

Hot-reload

Config is read fresh when a server instance is built, never baked in at build time — but when that happens depends on the transport:

TransportConfig is re-readEffect of editing a config file
httpper requestLive on the next call, no restart.
stdioonce per connectionTakes effect when the client reconnects.

HTTP builds a fresh server per request, so a dropped-in model, role, preset, or text override is picked up by the very next call. Stdio pins one server for the life of the connection, so the same edit lands when the client restarts Legion — which for most desktop MCP clients means reloading the server, not rebooting the machine.

Where this README says something is "live on the next call", read it as the HTTP behavior; over stdio it's the next connection.

Models — config/models/*.json

At least one model file is required — the server fails fast without one. Each JSON file becomes a tool, named after the slugified file name (config/models/fable.json → tool fable):

json
{   "model": "claude-fable-5",   "description": "Claude Fable — fast, creative, general purpose.",   "baseUrl": "https://api.example.com",   "apiKey": "sk-optional-per-model-key"}
  • model (required) — the deployed model id the endpoint routes to.
  • description — helps the calling AI pick the right model.
  • system — optional baseline system instructions baked into every call to this model.
  • baseUrl / apiKey — optional; omitted values fall back to DEFAULT_BASE_URL / DEFAULT_API_KEY. apiKey may reference an environment variable with the env: prefix — "apiKey": "env:GPT_KEY" reads GPT_KEY from the environment (unset/empty is a fatal startup error), so a key can stay out of the file entirely.
  • omitParams — optional list of request params to drop for this model, e.g. ["temperature"]. The server stays provider-agnostic: it never assumes which models reject which params — you declare each model's quirks here. Useful for reasoning models and some deployments that reject temperature.
  • reasoning — optional "minimal" | "low" | "medium" | "high", sent as reasoning.effort. Reasoning models can spend an entire maxTokens thinking and emit nothing; "low" may help leave room for an answer. The endpoint must support this parameter; it does not bypass content filtering.
  • maxTokens — optional hard output ceiling for this model. Every request sends min(requested, ceiling), whatever the caller or preset role asked for. For models that think harder the more room they get and come back empty.

Hot-drop: the directory is re-scanned per request — add or edit a model file and it's live on the next call, no restart.

Secrets & git: model files can contain API keys, so config/models/*.json is git-ignored. Copy a *.example.json (tracked, key-free, ignored by the scanner) to get started:

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

Roles — config/roles/*.md

Optional hot-droppable instruction files. Each .md file becomes a named role (slugified from filename). Drop a file, it's live on the next call. This repo ships skeptic.md, builder.md, judge.md, and short.md (a terse "answer immediately, no deliberation" role useful for constrained-output turns) as ready-to-use starters — edit or delete them freely (they hold no secrets).

Available selectors in tools become roleName, e.g. passing role: "skeptic" or using "model:skeptic" in quorum.models.

Presets — config/presets/*.json

Optional hot-droppable council recipes, one JSON file per preset (named after the slugified file name, like models). Each preset becomes its own tool — drop config/presets/code_review.json and a code_review tool appears on the next request. Each preset has a description, a roles list, and optional authoritative mode / synthesizer defaults. Each role defines its behavior inline — a role's description is its instructions (the behavior contract); a role with no description falls back to a matching config/roles/<role>.md file:

json
{   "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 }   ]}

The calling AI invokes the preset tool directly (e.g. code_review) and still writes the models selectors, assigning any model to any preset role. Presets are enforced: every selector must use a preset role and every role must be staffed within its cardinality, else the result is an error saying what to fix.

Keys:

KeyTypeDefaultDescription
descriptionstring | string[]requiredMCP description for the preset tool.
rolesPresetRole[]requiredRoles accepted by the preset.
mode"sequential" | "parallel" | "private" | "independent""sequential"Controls which prior turns each round speaker sees.
defaultRoundspositive integer1Rounds used when the call omits rounds.
synthesizerstringnoneRole that produces synthesis turns. A single-seat role is neutral and sits out the rounds; a multi-seat role (max > 1 or null) plays, and its first live seat synthesizes.
synthesizeEvery"end" | non-negative integer"end"Runs synthesis at the end or every Nth round.
eliminatorstringnoneNeutral role that issues eliminations. Required with eliminateEvery.
framerstringnoneNeutral role that opens and redirects the discussion.
reframeEvery"end" | non-negative integer"end"Reframes only at opening or every Nth round after opening.
closingStatementsbooleanfalseRuns a closing phase before final synthesis.
eliminateEverynon-negative integer | "spread"0Lets the eliminator remove one speaker every Nth round, or spread the cuts evenly. Preset-only.
eliminationsOptionalbooleanfalseLets the eliminator decline an elimination. Preset-only.
enterEverynon-negative integer0Starts one speaker per team, then adds one benched speaker every Nth round. Preset-only.
votestringnoneBallot instructions; enables anonymous voting.
voteEvery"end" | non-negative integer"end"Votes at the end or every Nth round.
voteVisibility"aggregate" | "ballots""aggregate"Includes only totals or also anonymized ballot choices in the transcript.
allowSelfVotebooleantrueIncludes each voter's own seat in its candidate menu.
voteByTeambooleanfalsePresents one choice per @team and aggregates votes by team.

Role object keys:

KeyTypeDefaultDescription
rolestringrequiredRole name used in model:role selectors.
descriptionstring | string[]matching role fileInline instructions; arrays are joined with newlines. Otherwise config/roles/<role>.md must exist.
minnon-negative integer1Minimum speakers; 0 makes the role optional.
maxpositive integer or null1Maximum speakers; null is unbounded.
maxTokenspositive integer8192Output limit for this role when the caller omits maxTokens. An explicit caller value overrides every role, including neutral roles. Includes hidden reasoning.
silentbooleanfalseLets the role observe and vote without speaking in normal rounds.
voterbooleanall eligible rolesRestricts anonymous ballots to marked roles when any role is marked.
candidatebooleanall eligible rolesRestricts ballot choices to marked roles when any role is marked.
closingbooleanall eligible rolesRestricts closings to marked roles; only the first marked speaker per team or unteamed role closes.
closingLastbooleanfalseRuns this closer after parallel closings with their statements in context; requires closing: true.
tagTeambooleanfalseRotates one marked speaker per @team into each normal round. Cannot combine with enterEvery.
cameobooleanfalseSpeaks in exactly one round — the call's cameoRound, else the midpoint — instead of every round. A run-in, not a regular.

For example, a courtroom call assigns lawyers to sides with @team tags. The first lawyer listed for each side gives that side's closing statement:

json
{   "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."   }}
Authoring notes

Things that bite when writing a preset:

  • Eliminations use exact labels. The judge copies one CUT <label> command from the eligible menu, then gives a reason on the next line. Optional cuts also allow KEEP ALL. Bare numbers and ambiguous selections are rejected. Local elimination prompt overrides must use this command format.
  • Required elimination can stall. Bounded cleanup retries do not guarantee a sole survivor. An unresolved run retains its summary and discussion, but returns isError: true and structuredContent.incomplete with the remaining labels. Optional-elimination contests are not subject to this requirement.
  • min/max count every speaker in that role, not per team. They bound the whole role across all teams, so a tag-team wrestler role that must cover sides from a 2v2 up to a 6v6 is min: 4, max: 12 — a 3v3 is just one valid staffing (6 wrestlers) inside that range, not its own max: 3. The engine cannot enforce "even sides" or "one per side" — say it in the description instead.
  • Neutral roles cannot be teamed. The eliminator, framer, and a single-seat synthesizer reject a @team tag, so a role that belongs to one side can't hold those jobs.
  • Some keys require others, and a violation is caught at load: synthesizeEvery needs a synthesizer, eliminateEvery needs an eliminator, reframeEvery needs a framer, closingLast needs closing, vote options need vote, and a single-seat synthesizer needs at least one other required role (it sits out the normal rounds). enterEvery and tagTeam cannot be combined — both decide who speaks.
  • A malformed preset is skipped, not fatal. It logs ❌ preset skipped — Invalid <file>: <reason> and every other tool still registers, so check the server log when a preset tool doesn't appear.
  • Cost is speakers × rounds serial model calls, so defaultRounds and a generous max multiply quickly. mode: "parallel" collapses each round to its slowest speaker, at the price of speakers no longer seeing each other within a round.

This repo ships these presets — edit or delete freely:

battle_royale
Free-for-all contest; an overseer crowns a winner.
brainstorm
Divergent idea generation across models.
bullying
One model defends a position while the rest gang up on it; an optional teacher rules on whether it held.
code_review
Structured multi-model code review.
courtroom
Team-tagged lawyers argue opposing sides, jurors vote by side, and a judge rules.
debate
Opposing sides argue a question to a synthesis.
double_blind
Independent blind panel — no one sees the others.
election
Candidates campaign, then the field decides by secret ballot — the anonymous vote is the verdict, not a judge's call. Optional incumbent defends a record; an optional silent electorate reads every round and votes without campaigning.
final_girl
Survivors culled one per round until one remains.
focus_group
Moderated panel that riffs off each other.
gauntlet
Private self-refinement race across rounds.
quick_take
Fast two-voice gut check — one direct answer, one line of pushback.
refine
Relay polish of an existing artifact.
tag_team
Tag team match: one @team-tagged wrestler is legal per team each round and the rest wait on the apron, rotating every round so a three-person side cycles through all three. An optional run_in hits the ring for a single round (book it with cameoRound), an optional announcer calls the match, and the ref names the winning team.
tiebreak
A decisive third voice resolves a stalemate.
war_games
A staggered-entry team cage match: @team-tagged combatants enter one at a time while a neutral ref calls fouls and names the winning team, with an optional booker who sets the match.
workshop
Differentiated creative team.

Which bundled presets register as tools is controlled by PRESETS — unset registers them all, and a local-only preset (a slug with no bundled counterpart) you add under your own config/presets/ is always registered regardless, since authoring one is the opt-in. A local file that reuses a bundled slug is a customization of that preset, not a new one, so it still obeys PRESETS. With no bundled presets and no local presets there are no preset tools — but note the bundled set is the base layer, so you get every bundled preset even when you ship no config/presets/ folder of your own.

Role text nudges output, it doesn't cap it — use maxTokens for a hard per-turn limit, and budget generously for reasoning models and multi-round quorums. tokenBudget is a different axis: it counts every call's input and output for the whole run, so size it from turns × (context + maxTokens) and set it above the estimate — input dominates once the transcript grows.

AI guidance — config/description.md

Optional markdown served to clients as MCP instructions — describe your models and when the AI should use each. See this repo's copy for a template. The literal {longRuns} marker is replaced with config/sync/long-runs.md or config/async/long-runs.md depending on ASYNC_TOOLS, so the delivery guidance (block-and-wait vs start/poll/cancel) tracks the mode without duplicating the rest of the file. A local description.md without the marker is served as written; a startup warning notes that mode guidance is missing.

Tool, field & message text — config/*.json and config/tools/*.md

The text a caller reads to drive Legion — tool and field descriptions, prompt scaffolding, runtime errors — lives in config, not code. (Result-shape descriptions in outputSchema, generated staffing lines, and startup errors stay in code.) Each file merges over the bundled JSON base per key, so override only what you want; open the shipped copies to see the full key set and {token} placeholders:

  • config/tools/quorum.md, poll.md, cancel.md — descriptions for the quorum tool and (async mode only) the poll / cancel tools. Delete one to fall back to its built-in string. Model tools describe themselves from their model file's description, and preset tools from the preset's own.
  • config/sync/long-runs.md, config/async/long-runs.md — the mode-specific block spliced into description.md at {longRuns}.
  • config/schema.json — input-field descriptions (prompt = shared fields, quorum = quorum-only; a quorum key wins on a name clash).
  • config/prompts.json — the prompt-shaping templates models read: role contract, context block, transcript header, round banners. Tune how strongly roles bind and how rounds are framed here.
  • config/errors.json — runtime error messages shown to the calling AI.

(Startup/config-validation errors stay in code — a message that reports a broken config file can't live inside it.)

Environment variables

VariableRequiredDescription
DEFAULT_BASE_URLno*API root for models without a baseUrl — the SDK appends /responses. E.g. https://api.openai.com/v1, https://<res>.openai.azure.com/openai/v1; a LiteLLM proxy works at its plain root.
DEFAULT_API_KEYno*API key for models without an apiKey. Stays server-side.
ALLOW_NO_MODELSnotrue boots even when no model files exist: zero model tools; quorum and preset tools register but fail on use until a config/models/*.json appears (hot-reloaded per request). For demos and registry sandboxes that only list tools. Default false — missing models stay fatal.
MCP_TRANSPORTnohttp (default) or stdio.
HOSTnoHTTP bind address (default 127.0.0.1). Set 0.0.0.0 to expose — then set ALLOWED_HOSTS.
ALLOWED_HOSTSnoComma-separated hostnames for DNS-rebinding protection on non-localhost binds.
PORTnoHTTP port (default 5000; ignored by stdio).
MAX_ROUNDSnoMax discussion rounds the quorum tool accepts (default 5).
MAX_TOKENSnoMaximum visible maxTokens per turn a caller may request; larger values are rejected (default 3000). Preset role limits above it are clamped. Per-model reasoning allotments are added on top.
MODEL_TIMEOUTnoPer-model-call timeout in ms (default 90000), so one stalled seat cannot stall a council. Retried once, so a seat's worst case is roughly double before it becomes a failed turn.
TOKEN_BUDGETnoDefault soft cumulative token budget for a quorum run (unset = no limit; per-call tokenBudget overrides).
DYNAMIC_ROLESnoAllow the calling AI to define ad-hoc quorum roles inline (default true).
PRESETSnoComma-separated allowlist of bundled preset slugs to register as tools (e.g. code_review,debate). Unset = every bundled preset, so upgrades never silently drop one; set = only these, so a newly shipped bundled preset never appears uninvited. Presets you add under your own config/presets/ are always registered — authoring one is the opt-in — while a local file sharing a bundled slug customizes that preset and still obeys the list. Unknown names are ignored.
LOG_LEVELnodebug | info | warn | error (default info).
ASYNC_TOOLSnotrue runs councils in the background: quorum and preset tools return a job handle immediately and poll / cancel tools are exposed. For hosts whose tool calls time out before a council can finish. Jobs live only in this process's memory. Default false.
TRUST_PROXY_AUTHnotrue binds each job to the caller named by X-MS-CLIENT-PRINCIPAL-* headers (Azure Container Apps Easy Auth). Only safe behind an ingress that strips client-supplied copies. Default false — any holder of a jobId may poll or cancel it.
JOB_MAX_ACTIVE / JOB_MAX_RETAINED / JOB_RETAIN_MS / JOB_POLL_INTERVAL_MS / JOB_SHUTDOWN_GRACE_MSnoAsync job bounds: concurrent councils (3), finished jobs kept (20), retention (1800000 ms), suggested poll cadence (10000 ms), SIGTERM drain (20000 ms).

* Every model must resolve a baseUrl and apiKey from its file or the defaults — validated at startup.

The server fails fast at startup on a missing/empty models directory (unless ALLOW_NO_MODELS=true), invalid model files, an unresolvable endpoint or key, or two file names that slugify to the same tool.

Routing

Every tool call is a stateless, one-shot Responses API request. Models whose endpoints natively speak Responses (OpenAI, Azure OpenAI / Foundry) set a baseUrl to be called directly; the rest fall back to the defaults — typically an OpenAI-compatible gateway like LiteLLM that bridges to their native APIs.

Logging

  • info (blue): server start and one metadata line per model call — model, latency, token usage, role, context presence. No prompt/response content.
  • debug (gray): additionally logs the full prompt and response (context is noted as present, not printed).
  • warn (orange) / error (red): fallbacks and failures.

Color is auto-disabled when stderr is not a TTY.

Run

One entrypoint; the transport comes from MCP_TRANSPORT (http is the default, set stdio for desktop MCP clients).

From npm (legion-mcp bin — run from a directory holding your config/):

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

Installed globally or as a dependency, the same binary is on PATH:

pwsh
npm install -g legion-mcplegion-mcp

Development (no build step, via tsx):

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

Production (compiled to bin/server.js):

pwsh
npm run buildnpm start           # httpnpm run start:stdio # stdio

Async mode — ASYNC_TOOLS=true

A council can run for minutes; hosted ChatGPT and Codex abandon a tool call long before that (observed ~124 s; Codex defaults to 60 s). With ASYNC_TOOLS=true the quorum and preset tools validate the request, start the council in the background, and return at once with a handle — structuredContent.jobId, the current state/phase, and a suggested pollIntervalMs. Two extra tools appear:

  • poll — read-only; returns the job's state, the answers and public notes completed so far, and (once terminal) result: the full council output in its usual shape. full: true adds the rendered transcript and timeline.
  • cancel — aborts in-flight model calls, returns whatever had completed, and settles the job as cancelled with no synthesis. Idempotent.

Individual model tools stay synchronous. Server instructions switch to the start/poll/cancel workflow so the calling model knows what to do. Jobs are held in process memory under JOB_* bounds — a restart loses them, and a request routed to a different replica cannot see them, so run a single instance (or pin sticky routing) when this mode is on. Set TRUST_PROXY_AUTH=true behind Azure Container Apps Easy Auth to bind each job to the authenticated caller; without it, possession of the jobId is the only access control.

Try it

List the tools with the MCP Inspector:

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

Use in VS Code

Add to your mcp.json — from npm:

json
{   "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"         }      }   }}

Or from a clone:

json
{   "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"         }      }   }}

For the HTTP transport, point your client at http://<host>:<PORT>/mcp.

Health

  • GET /health — cheap liveness: confirms the process is up and config loaded. Returns { status: "ok", name, version, models } (a count). Makes no external calls. This is what container HEALTHCHECKs and Kubernetes liveness/readiness probes should hit.
  • GET /health?deep — optional connectivity check: sends a tiny prompt to every model and reports per-model reachability (503 if any fail). Makes a real billable call per model, so use it manually — don't wire it to an automatic probe.

Deploy

Ready-to-use container deployment examples (Azure App Service, Azure Container Apps, Docker Compose, Kubernetes, and Compose + Caddy for HTTPS) live in examples/ — each installs Legion from npm and ships a complete drop-in config/.

來源:README.md,提交 55f2c8c

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

5
  1. v0.8.0最新Oct 7, 2026
  2. v0.7.0Sep 26, 2026
  3. v0.6.0Sep 24, 2026
  4. v0.5.0Sep 23, 2026
  5. v0.4.1Sep 16, 2026

評分

已滿足適用檢查項的 40%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分

    請在儲存庫或註冊中心條目中宣告授權條款。