A320 Cli
FreeNot checkedHeadless A320 systems simulator built on FlyByWire, with a CLI and an MCP server so LLM agents can detect and manage failures following real ECAM/QRH procedures
About
Headless A320 systems simulator built on FlyByWire, with a CLI and an MCP server so LLM agents can detect and manage failures following real ECAM/QRH procedures.
README
A headless digital twin of the A320's aircraft systems — no MSFS, no X-Plane — built on FlyByWire's open-source systems code, operable from a terminal by a human or over MCP by an LLM agent.
What is this
The real interaction between an A320's systems — electrical, hydraulic, pneumatic, fuel, APU, pressurization, and the FWC that raises ECAM warnings — lives in FlyByWire's Rust crates and does not need a flight simulator at runtime. This project compiles those crates natively, wraps them in a persistent tick loop, and exposes one control/observe API through two frontends:
- a CLI for a human to operate the aircraft from the terminal — flip switches, read buses, advance time, inject failures;
- an MCP server for an LLM to fly the closed loop: observe (ECAM + state), reason (QRH), act, advance, observe again.
The end goal is a reproducible environment for failure detection and management following real procedures (ECAM/QRH), designed as an LLM-agent benchmark. The research contribution is not the aircraft model (that is FlyByWire's) — it is the evaluable environment, the failure scenario suite, and the procedure-compliance scoring.
Demo
Real output of the current spike (core-rs/src/main.rs): cold & dark on the apron, batteries
on, external power on, then a transformer-rectifier failure injected — the network
reconfigures through the bus tie exactly like the real aircraft, while TR 1 itself drops out:
$ cargo run
[cold & dark] AC_1=false DC_1=false DC_BAT=false DC_HOT_1=true TR_1_OK=false
[baterias ON] AC_1=false DC_1=false DC_BAT=true DC_HOT_1=true TR_1_OK=false
[ext pwr ON] AC_1=true DC_1=true DC_BAT=true DC_HOT_1=true TR_1_OK=true
[fail TR 1] AC_1=true DC_1=true DC_BAT=true DC_HOT_1=true TR_1_OK=false
No simulator running. The whole aircraft systems stack is ticking inside one native process.
Architecture
One core, two frontends: the CLI and the MCP server are two windows onto the same control/observe API.
flowchart TB
subgraph frontends["Frontends"]
CLI["CLI REPL<br/>(human)"]
MCP["MCP server<br/>(LLM agent)"]
end
API["Control / Observe API<br/><code>set · get · step · read_ecam · inject_failure · snapshot</code>"]
subgraph core["Sim core — Rust (via PyO3)"]
HARNESS["Persistent harness + tick loop"]
REG["Variable registry<br/>(inputs / outputs by name)"]
FAIL["Failure injection<br/>(FBW FailureType)"]
FBW["Vendored FlyByWire crates<br/><code>systems</code> + <code>a320_systems</code><br/>elec · hyd · pneu · fuel · APU · press · FWC"]
end
CLI --> API
MCP --> API
API --> HARNESS
HARNESS --> REG
HARNESS --> FAIL
REG --> FBW
FAIL --> FBW
The systems logic is reused untouched from FlyByWire; this repo builds everything around it —
the analogy is that FBW provides the engine block, and this project builds the chassis
(headless harness), the dashboard (CLI/MCP) and wires the ignition (UpdateContext + variable
registry).
| Reused from FlyByWire | All systems logic: hydraulic pressurization, electrical bus feeding, APU start sequence, FWC warning logic, failure models |
| Built here | Native standalone build, persistent tick loop, variable registry, world-boundary inputs (UpdateContext), control/observe API, CLI, MCP server, scenario suite + scoring |
The agent loop
sequenceDiagram
participant Agent as LLM agent
participant MCP as MCP server
participant Sim as A320 sim core
loop until failure managed
Agent->>MCP: read_ecam / read_state
MCP->>Sim: query FWC warnings + system vars
Sim-->>Agent: "ELEC TR 1 FAULT" + bus states
Note over Agent: reason over QRH procedure
Agent->>MCP: set_control("...", value)
MCP->>Sim: write input variable
Agent->>MCP: advance(seconds)
MCP->>Sim: tick simulation
end
MCP tools exposed to the agent: set_control, read_state, read_ecam, advance,
inject_failure, list_failures, clear_failure, snapshot, list_controls.
Quickstart
Install Rust (if you don't have it):
winget install Rustlang.RustupOn Windows, rustup uses the MSVC target: if the Visual Studio Build Tools (C++) are missing, rustup itself will tell you during install (
winget install Microsoft.VisualStudio.2022.BuildTools). The exact toolchain (Rust 1.93.0) auto-installs on first build thanks torust-toolchain.toml.Vendor FlyByWire (pinned submodule, efficient blob-filtered + sparse clone):
.\scripts\bootstrap-vendor.ps1Build and run the spike:
cd core-rs cargo runThe first build takes several minutes (it compiles the FBW monorepo's dependencies).
(Optional) Python bindings — drive the same core from Python. Building now needs both toolchains (Rust + Python ≥ 3.9), since it compiles the Rust core into a Python extension:
cd bindings python -m venv .venv .\.venv\Scripts\Activate.ps1 pip install -e . python tests\test_smoke.pyThen
import a320_simexposes theSimclass (see bindings/README.md).(Optional) CLI — operate the aircraft from a terminal. After the bindings are installed in the same environment:
pip install -e cli/ a320-cli # or: python -m a320_cli
Operate it from the terminal (CLI)
The CLI is the human window onto the core: flip switches, read buses, advance
time, and watch the electrical network come alive. Commands map 1:1 onto the
API (set / get / step / run / env / snapshot / controls / vars /
watch); control names tab-complete from the curated catalog, and errors print a
one-line message instead of a traceback. Full command reference in
cli/README.md.
Worked example — the Phase-1 target scenario (cold & dark → battery → external power), typed straight into the REPL:
a320 [t= 1.0s]> set bat_1 on
bat_1 <- 1
a320 [t= 1.0s]> set bat_2 on
bat_2 <- 1
a320 [t= 1.0s]> watch ELEC_DC_BAT_BUS_IS_POWERED ELEC_AC_1_BUS_IS_POWERED
watching 2 var(s) at 5 Hz - Ctrl+C to stop
* ELEC_DC_BAT_BUS_IS_POWERED 1 <- DC BAT bus comes alive around t=2.0s
ELEC_AC_1_BUS_IS_POWERED 0 (AC stays dead: no AC source yet)
t= 2.00s
^C
[watch stopped at t=3.20s]
a320 [t= 3.2s]> set bus_tie auto # AUTO is mandatory: no seeding (D-007)
bus_tie <- 1
a320 [t= 3.2s]> set ext_pwr_avail 1
ext_pwr_avail <- 1
a320 [t= 3.2s]> set ext_pwr on
ext_pwr <- 1
a320 [t= 3.2s]> watch ELEC_AC_1_BUS_IS_POWERED ELEC_AC_2_BUS_IS_POWERED ELEC_DC_1_BUS_IS_POWERED ELEC_DC_2_BUS_IS_POWERED
watching 4 var(s) at 5 Hz - Ctrl+C to stop
* ELEC_AC_1_BUS_IS_POWERED 1 <- whole AC network wakes up around t=3.6s
* ELEC_AC_2_BUS_IS_POWERED 1
* ELEC_DC_1_BUS_IS_POWERED 1
* ELEC_DC_2_BUS_IS_POWERED 1
t= 3.60s
The * marks a powered bus. watch advances the sim at ~5 Hz and re-renders in
place, so you see contactors sequence — not just a before and an after.
Roadmap
| Phase | Scope | Status |
|---|---|---|
| 0 — Feasibility spike | Compile FBW's systems + a320_systems natively, instantiate the A320, tick it, read electrical vars, inject a failure |
Done — success criterion met (see Demo); zero patches to vendored code needed; 102 upstream electrical tests pass natively; stack settled (D-004) |
| 1 — Core + API + CLI | Persistent runtime over Simulation<A320>, variable registry, set/get/step API, human REPL; electrical vertical slice on ground |
Done — success criterion automated as an integration test (core-rs/tests/electrical_slice.rs); curated control catalog, PyO3 bindings (a320_sim) and REPL with live watch shipped; design note in docs/fase1-runtime.md (Spanish) |
| 2 — Failures + detection | inject_failure / list_failures, read_ecam |
Done — success criterion automated (core-rs/tests/generator_caution.rs): the APU generator fails and its caution appears. Stable failure ids (elec.tr.1) decoupled from FBW's enum (D-013). There is no FWC in the vendored Rust, so the ECAM catalog is ours — a rule engine over what FBW actually writes, with each rule declaring whether its logic is FBW's or ours (D-014, docs/fase2-ecam.md) |
| 3 — MCP server | Expose the API as MCP tools; end-to-end demo: hand an LLM a failed generator and watch it work the procedure | Done — nine tools over stdio (mcp/README.md), tested by spawning the server and driving it over the real protocol. An LLM resolved the APU GEN failure from the ECAM alone. What the agent cannot see is part of the contract: the active-failure list is deliberately not a tool, or the benchmark would measure reading instead of diagnosis (D-016) |
| 4 — More systems | Hydraulics, APU, fuel, engine start; richer world boundary (N2 input) | Done — the full cold & dark → engines running sequence runs on catalog names alone, automated as the closing integration test (core-rs/tests/cold_dark_to_engines_running.rs) and available to an agent as a320-mcp --start engines-running: batteries → APU + bleed → engine 1 on APU air → engine 2 off the crossbleed → engine generators take the network, all three hydraulic circuits pressurized, APU shut down, clean ECAM. The engine itself is ours — FBW's Rust only reads engine simvars — a deterministic first-order N2 spool (D-019); fuel is world state seeded once (D-018); engine starts demand real bleed air (D-020); panel resting states seeded (D-021) |
| 5 — Benchmark (research) | Scenario suite with QRH ground truth, trajectory-level compliance scoring, baselines + ablations | Next |
Architecture decisions are recorded in docs/decisiones.md (Spanish): FBW pin, why no msfs-rs decoupling was needed, PyO3 vs pure-Rust rationale. Project conventions live in CLAUDE.md (Spanish).
Repo layout
core-rs/ Rust core: harness + API + vendored FBW (pinned git submodule)
vendor/ flybywiresim/aircraft @ 13bce4b (GPLv3)
bindings/ PyO3 bindings (Phase 1)
cli/ Python REPL (Phase 1)
mcp/ MCP server (Phase 3)
scenarios/ Failure scenarios + QRH ground truth (Phase 5)
docs/ Design notes + decision log
scripts/ bootstrap-vendor.ps1
License & credits
GPLv3, inherited from the FlyByWire Simulations crates this project vendors and links against. All aircraft systems modeling credit goes to the FlyByWire team — this project would not exist without their decision to write the A320's systems in simulator-agnostic Rust.
The vendored FBW tree is pinned to a specific commit (13bce4b, see
docs/decisiones.md): benchmark reproducibility depends on it, so the pin
only moves with a recorded decision.
Installing A320 Cli
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/Santisoutoo/a320-cliFAQ
Is A320 Cli MCP free?
Yes, A320 Cli MCP is free — one-click install via Unyly at no cost.
Does A320 Cli need an API key?
No, A320 Cli runs without API keys or environment variables.
Is A320 Cli hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install A320 Cli in Claude Desktop, Claude Code or Cursor?
Open A320 Cli on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Fetch
Web content fetching and conversion for efficient LLM usage.
AWS KB Retrieval
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
by modelcontextprotocolSpring AI MCP Server
Provides auto-configuration for setting up an MCP server in Spring Boot applications.
llm-analysis-assistant
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
by xuzexin-hzCompare A320 Cli with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All ai MCPs
