Metadata-Version: 2.4
Name: xfoil-mcp
Version: 0.1.1
Summary: Domain-neutral MCP wrappers for running XFOIL analyses
Author: XFOIL MCP Maintainers
License: MIT License
Copyright (c) 2025, Three Little Birds
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Project-URL: Homepage, https://github.com/Three-Little-Birds/xfoil-mcp
Project-URL: Repository, https://github.com/Three-Little-Birds/xfoil-mcp
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp>=1.20
Requires-Dist: fastapi<1.0,>=0.121
Requires-Dist: pydantic>=2.11
Provides-Extra: dev
Requires-Dist: pytest>=8.4; extra == "dev"
Requires-Dist: ruff>=0.14; extra == "dev"
Requires-Dist: httpx>=0.28; extra == "dev"
Dynamic: license-file
# xfoil-mcp - Aerodynamic polars on-call for your MCP agents
<p align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
<a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%2B-3776AB.svg" alt="Python 3.10 or newer"></a>
<a href="https://github.com/Three-Little-Birds/xfoil-mcp/actions/workflows/ci.yml"><img src="https://github.com/Three-Little-Birds/xfoil-mcp/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
<img src="https://img.shields.io/badge/MCP-tooling-blueviolet.svg" alt="MCP tooling badge">
</p>
> **TL;DR**: Wrap [XFOIL](https://web.mit.edu/drela/Public/web/xfoil/) in an MCP-native service so agents can request lift/drag/moment polars without touching shell scripts.
## Table of contents
1. [What it provides](#what-it-provides)
2. [Quickstart](#quickstart)
3. [Run as a service](#run-as-a-service)
4. [Agent playbook](#agent-playbook)
5. [Stretch ideas](#stretch-ideas)
6. [Accessibility & upkeep](#accessibility--upkeep)
7. [Contributing](#contributing)
## What it provides
| Scenario | Value |
|----------|-------|
| Quick experiments | Compute lift/drag/moment polars from an airfoil file or NACA code without launching XFOIL manually. |
| MCP integration | STDIO/HTTP transports that follow the Model Context Protocol so ToolHive or other clients can call XFOIL programmatically. |
| Audit trail | Responses include on-disk work directories and metadata so you can trace which inputs produced a given polar. |
## Quickstart
### 1. Install dependencies
```bash
uv pip install "git+https://github.com/Three-Little-Birds/xfoil-mcp.git"
```
Download XFOIL from the [official MIT site](https://web.mit.edu/drela/Public/web/xfoil/) and place the executable on your `PATH` (or point to it explicitly):
```bash
export XFOIL_BIN=/path/to/xfoil
```
### 2. Compute your first polar
```python
from pathlib import Path
from io import StringIO
import pandas as pd
from xfoil_mcp import PolarRequest, compute_polar
airfoil_path = Path("examples/naca2412.dat") # bundled sample airfoil
request = PolarRequest(
airfoil_name="naca2412",
airfoil_data=airfoil_path.read_text(encoding="utf-8"),
alphas=[-2 + 0.5 * i for i in range(29)], # -2 .. 12 in 0.5° steps
reynolds=1.2e6,
mach=0.08,
)
response = compute_polar(request)
polar_csv_path = Path("polar.csv")
polar_csv_path.write_text(response.csv, encoding="utf-8")
print("CSV stored at", polar_csv_path)
```
Inspect the first few rows:
```python
df = pd.read_csv(StringIO(response.csv), comment="#")
print(df.head())
```
- The bundled `examples/naca2412.dat` follows cosine-spaced sampling (identical to XFOIL's `PANE` output) so you can drop it into your own scripts without re-gridding.
- The CSV header is normalised to `alpha, CL, CD, CM`. XFOIL may append extra columns (e.g. `CDp`, `Cl/Cd`, transition locations); those appear after the first four fields and remain untouched.
## Run as a service
### CLI (STDIO / Streamable HTTP)
```bash
uvx xfoil-mcp # runs the MCP over stdio
# or python -m xfoil_mcp
python -m xfoil_mcp --transport streamable-http --host 0.0.0.0 --port 8000 --path /mcp
```
Use `python -m xfoil_mcp --describe` to view metadata and exit.
> **Tip (macOS/Linux):** building XFOIL natively requires XQuartz/X11 headers. To avoid that setup, run the quickstart inside the repo's Docker recipe:
> ```bash
> docker run --rm -v "$PWD/extern/xfoil-mcp:/workspace/xfoil-mcp" python:3.13-slim bash -lc '
> set -euo pipefail
> apt-get update && apt-get install -y --no-install-recommends xfoil build-essential \
> && pip install --no-cache-dir pandas /workspace/xfoil-mcp \
> && python - <<"PY"
> from pathlib import Path
> from io import StringIO
> import pandas as pd
> from xfoil_mcp import PolarRequest, compute_polar
>
> airfoil_path = Path("examples/naca2412.dat")
> request = PolarRequest(
> airfoil_name="naca2412",
> airfoil_data=airfoil_path.read_text(encoding="utf-8"),
> alphas=[-2 + 0.5 * i for i in range(29)],
> reynolds=1.2e6,
> mach=0.08,
> )
> response = compute_polar(request)
> print(pd.read_csv(StringIO(response.csv), comment="#").head())
> PY
> '
> ```
### Handling failures
`compute_polar` raises `RuntimeError` when XFOIL fails to emit a polar (common causes: laminar separation, too few iterations, or Reynolds numbers below ~5e4). The stderr/stdout from XFOIL is preserved in the exception message—increase `ITER`, seed a better initial airfoil mesh, or adjust `alpha_start_deg/alpha_step_deg` in response. Non-zero exit codes that still produce a polar are annotated in the CSV with a leading `# xfoil exit code ...` comment so you can decide whether to discard or accept the run.
### FastAPI (REST)
```bash
uv run uvicorn xfoil_mcp.fastapi_app:create_app --factory --port 8001
```
Browse `http://127.0.0.1:8001/docs` to test requests and download CSVs.
### python-sdk tool (STDIO / MCP)
```python
from mcp.server.fastmcp import FastMCP
from xfoil_mcp.tool import build_tool
mcp = FastMCP("xfoil-mcp", "XFOIL polar analysis")
build_tool(mcp)
if __name__ == "__main__":
mcp.run()
```
Launch:
```bash
uv run mcp dev examples/xfoil_tool.py
```
Connect any MCP-compatible agent (Cursor, Claude Desktop, Windsurf, ...) and ask for polars on demand.
### ToolHive smoke test
Requires `XFOIL_BIN` pointing to the XFOIL executable:
```bash
export XFOIL_BIN=/path/to/xfoil
uvx --with 'mcp==1.20.0' python scripts/integration/run_xfoil.py
# ToolHive 2025+ defaults to Streamable HTTP; match that transport when registering
# the workload manually to avoid the legacy SSE proxy failures.
```
## Agent playbook
- **Batch sweeps** - iterate through a directory of `.dat` files and persist each polar to object storage.
- **Optimisation loops** - embed the tool inside a genetic algorithm; typed responses keep mutation + evaluation deterministic.
- **Visualisation** - feed `response.csv_path` into Plotly or Matplotlib to plot `Cl` vs. `Cd` without manual parsing.
## Stretch ideas
1. Compare multiple foils by merging CSVs into a parquet dataset for notebook analysis.
2. Pair with `ctrltest-mcp` to explore control implications from polar derivatives.
3. Schedule nightly polars via CI and publish artefacts for downstream agents.
## Accessibility & upkeep
- Tests mock XFOIL so they run quickly: `uv run pytest`.
- Use `uv run ruff check .` before submitting changes.
## Contributing
1. Fork and `uv pip install --system -e .[dev]`.
2. Run the formatting and test suite.
3. Open a PR with before/after polar snippets so reviewers can verify quickly.
MIT license - see [LICENSE](LICENSE).