Metadata-Version: 2.5
Name: simconnect-mcp
Version: 1.0.0
Summary: MCP server for MSFS SimConnect — add-on development companion
Project-URL: Homepage, https://github.com/robin24/simconnect-mcp
Project-URL: Repository, https://github.com/robin24/simconnect-mcp
Project-URL: Issues, https://github.com/robin24/simconnect-mcp/issues
Author-email: Robin Kipp <robin@robin-kipp.net>
License-Expression: AGPL-3.0-or-later
License-File: LICENSE.txt
Keywords: flight-simulator,flightsim,mcp,mobiflight,model-context-protocol,msfs,pmdg,simconnect
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Games/Entertainment :: Simulation
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: mcp<2,>=1.26
Requires-Dist: simconnect>=0.4.26
Description-Content-Type: text/markdown

# SimConnect MCP Server

<!-- mcp-name: io.github.robin24/simconnect-mcp -->

An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that connects AI coding agents to Microsoft Flight Simulator via SimConnect. This server is built for **add-on development** — full read/write access to SimVars, L-vars, events, calculator code execution, and embedded documentation.

## What It Does

- **Read/write any SimVar** — altitude, heading, speed, autopilot settings, engine parameters, and 1,080+ more from a comprehensive built-in catalog, with unit selection and honest failures on a rejected write
- **Read/write L-vars** — aircraft-specific local variables used by add-on developers (Fenix A320, FlyByWire A32NX, PMDG, etc.), including live enumeration of what the loaded aircraft has actually registered
- **PMDG 777 and 737 NG3 native SDK support** — direct access to all aircraft data fields and control events via the PMDG SDK Client Data Areas, plus CDU screen reading with colors/formatting
- **Trigger events** — toggle switches, set autopilot modes, control lights, fire custom MobiFlight events
- **Execute RPN calculator code** — run arbitrary Reverse Polish Notation code directly in the sim
- **Search and discover variables** — searchable catalogs of SimVars, events, and aircraft-specific L-vars with human-readable names and valid values
- **Facilities lookup** — nearby airports sorted by distance and detail lookup for a specific airport, waypoint, NDB or VOR by ICAO identifier, read directly from SimConnect's facility subscription
- **Flight and scenario tools** — load and save `.FLT` flights, load a `.PLN` flight plan, and spawn an AI aircraft or object, for scripting test scenarios instead of setting them up by hand
- **Embedded documentation** — SimConnect reference docs served as MCP resources, available offline
- **Aircraft-specific catalogs** — pre-built variable databases with panel groupings, display names, and value descriptions. Ships with 1,607 PMDG 777 variables (777-200LR/200F/300ER/F) and 1,861 PMDG 737 NG3 variables (-600/700/800/900 incl. BBJ/BDSF/BCF).
- **HubHop integration** — search the [MobiFlight HubHop](https://hubhop.mobiflight.com) community preset database directly as an MCP tool, or use the built-in client to generate and extend L-var catalogs for any supported aircraft

## Prerequisites

- **Microsoft Flight Simulator** (MSFS 2020 or 2024) running on the same machine
- **[uv](https://docs.astral.sh/uv/)** — provides `uvx`, which fetches the server and its Python runtime for you
- **MobiFlight WASM Module** (optional, for L-var support) — install in your MSFS Community folder. Download from [MobiFlight](https://www.mobiflight.com/)

## Installation

Once [uv](https://docs.astral.sh/uv/) is installed, no separate install step is
needed — `uvx` fetches and runs the server on demand:

```bash
uvx simconnect-mcp
```

That starts the server on stdio, which is what an MCP client does for you. Run
it by hand only to check that it starts; press Ctrl+C to stop.

To pin a version, use `uvx simconnect-mcp@1.0.0`.

### From source (for development)

```bash
git clone https://github.com/robin24/simconnect-mcp.git
cd simconnect-mcp

# Creates the virtual environment and installs the dev group too
uv sync
```

## Configuring as an MCP Server

### Claude Code

**Via CLI:**

```bash
claude mcp add --transport stdio simconnect -- uvx simconnect-mcp
```

Or to make it available across all projects:

```bash
claude mcp add --transport stdio --scope user simconnect -- uvx simconnect-mcp
```

**Via JSON** (`~/.claude/settings.json` or project-level `.claude/settings.json`):

```json
{
  "mcpServers": {
    "simconnect": {
      "command": "uvx",
      "args": ["simconnect-mcp"]
    }
  }
}
```

### OpenAI Codex CLI

**Via CLI:**

```bash
codex mcp add simconnect -- uvx simconnect-mcp
```

**Via JSON:**

```json
{
  "mcpServers": {
    "simconnect": {
      "command": "uvx",
      "args": ["simconnect-mcp"]
    }
  }
}
```

### Gemini CLI

**Via CLI:**

```bash
gemini mcp add --transport stdio simconnect -- uvx simconnect-mcp
```

**Via JSON** (`~/.gemini/settings.json`):

```json
{
  "mcpServers": {
    "simconnect": {
      "command": "uvx",
      "args": ["simconnect-mcp"]
    }
  }
}
```

### MCP Inspector (Interactive Testing)

```bash
uv run mcp dev src/simconnect_mcp/server.py
```

> **Note:** To run a local checkout instead of the published package, replace
> `uvx simconnect-mcp` with `uv run --directory /path/to/simconnect-mcp simconnect-mcp`,
> using the absolute path to your clone.

## Tools (32)

Every tool is prefixed `msfs_`, carries explicit `readOnlyHint`/`destructiveHint` annotations, and returns a typed result or a structured error — never a fabricated success. The tables below are generated from the live server (name, kind, and title come straight from each tool's `ToolAnnotations`); descriptions are the one-line summary from the tool's own docstring:

```bash
uv run python -c "
import asyncio
from simconnect_mcp.server import mcp
async def m():
    for t in sorted(await mcp.list_tools(), key=lambda x: x.name):
        kind = 'read' if t.annotations.readOnlyHint else 'write'
        print(f'| \`{t.name}\` | {kind} | {t.annotations.title} |')
asyncio.run(m())"
```

### Connection (3)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_connect` | write | Establish a SimConnect connection to MSFS |
| `msfs_disconnect` | write | Close the SimConnect connection to MSFS |
| `msfs_get_connection_status` | read | Check connection state, whether the sim is running/paused |

### SimVar Operations (6)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_get_simvar` | read | Read a SimVar value by name, in the requested unit |
| `msfs_set_simvar` | write | Write a value to a settable SimVar — fails honestly rather than reporting success on a rejected write |
| `msfs_get_simvars_bulk` | read | Read several SimVars in one call; one failing entry doesn't abort the rest |
| `msfs_search_simvars` | read | Search the SimVar catalog by keyword |
| `msfs_list_simvar_categories` | read | List every SimVar category with its variable count |
| `msfs_watch_simvar` | read | Sample a SimVar over time, returning a time series for debugging |

**Units:** `msfs_get_simvar` (and `msfs_set_simvar`) accept an optional `unit` argument and always report the unit actually used in the result. Omit `unit` and the bundled SimVar catalog supplies a sensible default for that variable; for a variable the catalog doesn't know, the default is `"number"`.

### Event Operations (3)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_trigger_event` | write | Fire a SimConnect event, with an optional parameter |
| `msfs_search_events` | read | Search SimConnect events by keyword, optionally filtered by category |
| `msfs_trigger_custom_event` | write | Fire a custom event as a key event through the MobiFlight WASM module |

### L-Var Operations (6)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_get_lvar` | read | Read an L-var (local variable) value from the current aircraft |
| `msfs_set_lvar` | write | Write a value to an L-var on the current aircraft |
| `msfs_list_lvars` | read | Enumerate the L-vars the currently loaded aircraft has registered |
| `msfs_execute_calculator_code` | write | Execute RPN calculator code in the simulator |
| `msfs_search_lvars` | read | Search known aircraft L-vars by keyword across the bundled catalogs |
| `msfs_browse_lvar_catalog` | read | Browse the aircraft L-var catalogs — list catalogs, the panels in one, or the variables on one panel |

`msfs_list_lvars` asks the MobiFlight WASM module to enumerate what the aircraft has *actually* registered, live — but the module caps its reply at 1000 names and still reports the list as complete when it truncates. A busy add-on setup (GSX and similar) can crowd the aircraft's own variables out of the response entirely; watch for `truncated: true`. Treat the catalogs and the live listing as a starting point, not a guaranteed inventory — `msfs_get_lvar` reads any name you supply, whether or not it surfaced in either one.

### Aircraft State (1)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_get_aircraft_snapshot` | read | Read a snapshot of the current aircraft state (position, speed, attitude, and key systems in one call) |

### Facilities (2)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_get_nearby_airports` | read | List airports near a point, nearest first |
| `msfs_get_facility_info` | read | Look up one airport, waypoint, NDB or VOR by ICAO identifier |

Airports are world-wide (SimConnect returns the full 85,249-airport list, cached after the first collection). Waypoints, NDBs and VORs are scoped to wherever the aircraft currently is, so they are re-collected on every call rather than cached — a cached navaid list would keep answering for the aircraft's *previous* position after a reposition or a flight.

### Utilities (2)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_send_sim_text` | write | Display a text overlay message in the simulator (debug feedback) |
| `msfs_set_aircraft_position` | write | Reposition the aircraft (test scenario setup) |

### PMDG 777 / 737 NG3 (3)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_get_pmdg_var` | read | Read a PMDG aircraft data field by name — switches, MCP values, fuel qty, FMC data (777 or 737 NG3) |
| `msfs_get_pmdg_cdu` | read | Read a PMDG CDU screen as text rows with per-cell color and formatting |
| `msfs_send_pmdg_event` | write | Send a PMDG control event — toggle a switch, press a button, set a selector (777 or 737 NG3) |

These tools use the PMDG SDK Client Data Areas for direct binary access to the aircraft state — bypassing the MobiFlight L-var bridge. The right SDK (777 or 737 NG3) is auto-detected from the loaded aircraft, or can be forced with the `variant` argument. Requires `EnableDataBroadcast=1` and `EnableCDUBroadcast.N=1` in the aircraft's options.ini (`777_Options.ini` or `737NG3_Options.ini`). The 777 has three CDUs (Capt/Center/F-O); the 737 NG3 has two (Capt/F-O). See [PMDG 777 SDK Reference](src/simconnect_mcp/docs/pmdg_777.md) and [PMDG 737 NG3 SDK Reference](src/simconnect_mcp/docs/pmdg_737.md) for details.

### HubHop (2)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_search_hubhop` | read | Search the MobiFlight HubHop community preset database |
| `msfs_list_hubhop_aircraft` | read | List the aircraft that HubHop has presets for |

Unlike every other tool, these two reach HubHop's HTTP API rather than the simulator, so they work with MSFS closed. The first call downloads and caches the full preset database (~17 MB); later calls in the same server process are served from that in-memory cache, which both tools share and which re-fetches on its own every 6 hours. Either tool accepts `refresh=true` to force an immediate re-fetch.

### Flight and Scenario (4)

| Tool | Kind | Description |
|------|------|-------------|
| `msfs_load_flight` | write | Load a saved flight, replacing the current one |
| `msfs_save_flight` | write | Save the current flight to a `.FLT` file — refuses to overwrite an existing file unless `overwrite=true` is passed explicitly |
| `msfs_load_flight_plan` | write | Load a `.PLN` flight plan into the aircraft's GPS/FMS, replacing whatever plan is currently active |
| `msfs_create_ai_object` | write | Spawn an AI aircraft or object at a position — reports whether SimConnect accepted the request, which is not the same as the object existing (MSFS ignores an unmatched title silently) |

These wrap the underlying SimConnect flight/scenario file operations for scripting test setups (e.g. "load this approach", "save the current state", "spawn traffic nearby") rather than driving them by hand in the sim's own UI. `msfs_create_ai_object` is confirmed live end-to-end — spawned, verified to answer a targeted SimVar request, then removed again — by `tests/live/test_live_flight.py`. `msfs_save_flight` was also confirmed live, including the multi-second post-save SimConnect stall documented in CLAUDE.md's Known Sim Behaviours; its own live tests were retired in the 2026-08-29 live-suite trim once that finding was captured there, since what remained (the overwrite-guard logic) is pure Python already covered by `tests/test_flight.py`'s mocks. `msfs_load_flight` and `msfs_load_flight_plan` are mock-only by design — see `tests/live/test_live_flight.py`'s module docstring for why. See [Running the live tests](#running-the-live-tests) below.

## Variable Catalogs

The server ships with comprehensive variable catalogs for search and discovery, so that AI agents can find the right variable names without guessing.

### SimVar Catalog (1,080+ variables)

The built-in SimVar catalog covers all documented MSFS simulation variables across 25 categories:

| Category | Vars | Category | Vars |
|----------|------|----------|------|
| Aircraft Engine | 112 | Aircraft Avionics | 122 |
| Aircraft Fuel | 48 | Miscellaneous | 162 |
| Aircraft Controls | 50 | Landing Gear | 54 |
| Autopilot | 39 | Flight Instrumentation | 45 |
| Aircraft Lights | 25 | Aircraft Position and Speed | 46 |
| Camera | 34 | Radio Navigation | 66 |
| Services | 42 | Aircraft Electrics | 47 |
| Flight Model | 11 | Aircraft Systems | 24 |
| Environment | 14 | *and more...* | |

The catalog is sourced from both the SimConnect Python package and the [official MSFS SDK documentation](https://docs.flightsimulator.com/html/Programming_Tools/SimVars/Simulation_Variables.htm), ensuring complete coverage including camera controls, ground services, circuit breakers, GPS/NAV/COM radios, and flight model variables.

### Aircraft L-Var Catalogs

L-var catalogs provide searchable, human-readable databases for specific aircraft add-ons. The server picks one in three steps, and tells you in the result `message` which step answered — the three carry very different confidence:

1. **Probe the PMDG SDK client data area.** Authoritative, and independent of what the aircraft calls itself. This is the step that matters, because PMDG airframes do *not* identify themselves as PMDG: a real PMDG 737-800 reports `TITLE` as `737-800 PAX SSW TC`, with no vendor name anywhere in it. Matching titles alone never worked for them.
2. **Match `title_pattern` against `TITLE`/`ATC_MODEL`.** The fallback, and the only mechanism available to a catalog you supply yourself.
3. **Search every catalog and say so.** Results then carry a `Catalog` column so you can see which aircraft each row belongs to, rather than reading another aircraft's variables as if they were yours.

Pass `catalog=<key>` to scope a search explicitly, or call `msfs_browse_lvar_catalog` with no arguments to list the available keys.

> The obvious shortcut — broadening `title_pattern` to `737` — is deliberately **not** taken. It would match the iFly 737 MAX and Asobo's own 737 just as happily, and hand their users a PMDG catalog for an aircraft that isn't a PMDG. An honest "no catalog detected" beats a confidently wrong one.

**Included catalogs:**

| Aircraft | Variables | Panels | Source |
|----------|-----------|--------|--------|
| PMDG 777 (all variants) | 1,607 | 28 | SDK header parse + HubHop |
| PMDG 737 NG3 (-600/700/800/900/BBJ/BDSF/BCF) | 1,861 | 27 | SDK header parse + HubHop |

Each variable includes a display name, category, writability flag, and (where applicable) a map of valid values.

No Fenix catalog ships — a prior `fenix_a320.json` (1,433 plain L-vars) was removed in favor of HubHop's own broader, community-maintained `FenixSim` coverage (2,273 presets, current the moment Fenix ships an update, where a bundled snapshot would only go stale). Search it live with `msfs_search_hubhop(vendor="FenixSim")`, or regenerate a local catalog file with the HubHop client below and drop it into `src/simconnect_mcp/data/` — every `*.json` there is auto-discovered on startup, so it works immediately with no code change.

### Adding New Aircraft Catalogs

The fastest way to add a new aircraft is via the built-in HubHop client:

```bash
# See what's available
python -m simconnect_mcp.data.hubhop --list-vendors

# Generate a catalog (example: FlyByWire A32NX)
python -m simconnect_mcp.data.hubhop \
    --vendor "FlyByWire Simulations" \
    --aircraft "A320neo" \
    --aircraft-name "FlyByWire A32NX" \
    --title-pattern "A32NX" \
    -o fbw_a32nx.json
```

Or update an existing one:

```bash
python -m simconnect_mcp.data.hubhop --vendor FenixSim --merge fenix_a320.json
```

You can also use the Python API:

```python
from simconnect_mcp.data.hubhop import HubHopClient

client = HubHopClient()
presets = client.fetch_presets(vendor="FlyByWire Simulations", aircraft="A320neo")
catalog = client.build_catalog(presets, aircraft="FlyByWire A32NX", title_pattern="A32NX")
client.save_catalog(catalog, "fbw_a32nx.json")
```

Or create catalogs manually by placing a JSON file in `src/simconnect_mcp/data/`. All `*.json` files are auto-discovered on startup. See [docs/extending-catalogs.md](docs/extending-catalogs.md) for the full guide, JSON schema, and best practices.

## Resources

| URI | Type | Content |
|-----|------|---------|
| `simconnect://docs/overview` | `text/markdown` | SimConnect architecture and key concepts |
| `simconnect://docs/simvars/{category}` | `text/markdown` | SimVar reference by category |
| `simconnect://docs/events/{category}` | `text/markdown` | Event reference by category |
| `simconnect://docs/rpn` | `text/markdown` | RPN calculator syntax guide |
| `simconnect://docs/lvars` | `text/markdown` | L-var usage for add-on development |
| `simconnect://docs/best-practices` | `text/markdown` | Common pitfalls and performance tips |
| `simconnect://docs/pmdg/{variant}` | `text/markdown` | PMDG SDK reference; `variant` is `777` or `737` (a leading `B` is accepted, case-insensitive) |
| `simconnect://state/connection` | `application/json` | Live connection status |
| `simconnect://state/aircraft` | `application/json` | Current aircraft title, type, and position |

## Prompts

| Prompt | Purpose |
|--------|---------|
| `debug_simvar` | Step-by-step guide for debugging a misbehaving SimVar |
| `analyze_aircraft_vars` | Enumerate and categorize all L-vars on current aircraft |
| `create_addon_boilerplate` | Generate add-on starter code by type |
| `rpn_helper` | Translate natural language to RPN calculator code |
| `simconnect_code_review` | Review SimConnect code for common issues |

## Project Structure

```
src/simconnect_mcp/
├── server.py              # FastMCP instance, lifespan, tool registration
├── connection.py          # SimConnectManager singleton + native set_lvar
├── dispatch.py            # SimConnectDispatcher — owns the SimConnect dispatch loop
├── simvar_access.py       # SimVarAccessor — generic SimVar reads/writes via data definitions
├── facilities.py          # Facility (*_LIST) message parsing and accumulation
├── pmdg.py                # PMDG 777 SDK structs, CDU rendering, data manager
├── pmdg_ng3.py            # PMDG 737 NG3 SDK structs, CDU rendering, data manager
├── tools/
│   ├── __init__.py        # @handle_simconnect_errors, @require_connection decorators
│   ├── connection_tools.py # connect_to_sim, disconnect_from_sim, get_connection_status
│   ├── simvars.py         # SimVar CRUD via SimVarAccessor (1,080+ vars in the catalog)
│   ├── events.py          # Event trigger/search + built-in catalog
│   ├── lvars.py           # L-var read/write/search/enumerate/browse catalogs/calculator code
│   ├── pmdg.py            # PMDG tools — auto-dispatch to 777 or 737 NG3
│   ├── aircraft.py        # get_aircraft_snapshot — combined state snapshot
│   ├── facilities.py      # Airport/navaid lookup on top of facilities.py
│   ├── flight.py          # load/save flight, load flight plan, spawn AI object
│   ├── hubhop.py          # HubHop preset search, exposed as MCP tools
│   ├── utilities.py       # send_sim_text, set_aircraft_position
│   ├── models.py          # Shared Pydantic result/error models
│   └── formatting.py      # Pagination and markdown-table helpers
├── data/
│   ├── catalog.py         # L-var catalog loader and search engine
│   ├── simvar_catalog.py  # SimVar catalog loader, unit resolution, string-var detection
│   ├── hubhop.py          # MobiFlight HubHop API client (CLI + library), in-memory cache
│   ├── pmdg_777.json      # PMDG 777 catalog (1,607 vars, 28 panels)
│   ├── pmdg_737.json      # PMDG 737 NG3 catalog (1,861 vars, 27 panels)
│   └── simvars_catalog.json  # Built-in SimVar catalog (1,080+ vars, 25 categories)
├── vendor/                # Byte-faithful to upstream except two documented local changes
│   ├── simconnect_mobiflight.py          # unmodified
│   └── mobiflight_variable_requests.py   # log-level demotion + response-channel routing
└── docs/                  # Embedded documentation, served as MCP resources
    ├── overview.md, simvars.md, events.md, rpn.md, lvars.md, best_practices.md
    ├── pmdg_777.md        # PMDG 777 SDK reference
    └── pmdg_737.md        # PMDG 737 NG3 SDK reference
```

The PMDG catalogs are regenerated from the SDK headers via `scripts/parse_pmdg_sdk.py` — it auto-detects the struct name and CDU count, so the same script handles both 777 and 737 NG3.

## Development

```bash
# Run the mocked test suite (no MSFS required; live tests are deselected by default)
uv run pytest

# Run tests with verbose output
uv run pytest -v

# Run only specific test files
uv run pytest tests/test_hubhop.py    # HubHop client tests
uv run pytest tests/test_simvars.py   # SimVar catalog tests

# Lint
uv run ruff check src/ tests/
```

Tests mock SimConnect so the suite above runs without MSFS. `tests/conftest.py` provides a mock SimConnect with realistic SimVar values.

### Running the Live Tests

A second suite under `tests/live/` exercises the real SimConnect DLL and is marked `@pytest.mark.live`. `pyproject.toml` deselects it by default (`addopts = "-m 'not live'"`), so it never runs on a machine without MSFS — including CI.

This suite is deliberately small and stays that way on purpose: a test belongs here only if a self-consistent mock could agree with itself regardless of whether the code is right — for example, a round trip that writes and reads back a single L-var name proves nothing about encoding, because a mangled datum name would make the write and the read-back agree with each other on the wrong variable (see `test_two_distinct_lvars_do_not_collide` in `tests/live/test_live_lvars.py`). What's left after that filter is real DLL/DLL-adjacent behaviour a mock can only assume rather than verify: unit conversion against the physical constant, wire-decode of structs whose third-party bindings this project has already caught wrong once, which real SimVars/events actually accept a write versus reject or silently ignore it, undocumented MobiFlight/WASM protocol quirks, and whether a real PMDG's binary client-data area answers a probe the way its struct decode expects.

Where a live finding can instead be frozen into a committed fixture and replayed offline, that's preferred over a live test: `tests/fixtures/facilities/` holds real SimConnect wire bytes captured once, replayed by the *mocked* `tests/test_facilities_parsing.py` on every run, with no simulator and no flakiness — it pins the same discovery deterministically instead of depending on whatever aircraft happens to be loaded that day.

```bash
uv run pytest -m live
```

Requires MSFS running with an aircraft loaded; a test whose connection attempt fails is skipped rather than failed (see `tests/live/conftest.py`'s `live_manager` fixture). `tests/live/test_live_pmdg.py`'s tests need a real PMDG 737/777 loaded and skip — rather than fail — when the loaded aircraft doesn't look like one (see that file's gate in `tests/live/conftest.py`).

## License

Copyright (C) 2025-2026 Robin Kipp

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version. See [LICENSE.txt](LICENSE.txt) for the full text.

This project is AGPL-3.0 because it depends on
[Python-SimConnect](https://github.com/odwdinc/Python-SimConnect), which is
itself AGPL-3.0 licensed.

The vendored MobiFlight bridge in `src/simconnect_mcp/vendor/` is a separate
work, MIT licensed by Koseng — see
[`src/simconnect_mcp/vendor/MOBIFLIGHT_LICENSE`](src/simconnect_mcp/vendor/MOBIFLIGHT_LICENSE).
