xfoil-mcp - Aerodynamic polars on-call for your MCP agents
TL;DR: Wrap XFOIL in an MCP-native service so agents can request lift/drag/moment polars without touching shell scripts.
Table of contents
- What it provides
- Quickstart
- Run as a service
- Agent playbook
- Stretch ideas
- Accessibility & upkeep
- 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
uv pip install "git+https://github.com/Three-Little-Birds/xfoil-mcp.git"
Download XFOIL from the official MIT site and place the executable on your PATH (or point to it explicitly):
export XFOIL_BIN=/path/to/xfoil
2. Compute your first polar
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:
df = pd.read_csv(StringIO(response.csv), comment="#")
print(df.head())
- The bundled
examples/naca2412.datfollows cosine-spaced sampling (identical to XFOIL'sPANEoutput) 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)
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:
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)
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)
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:
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:
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
.datfiles 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_pathinto Plotly or Matplotlib to plotClvs.Cdwithout manual parsing.
Stretch ideas
- Compare multiple foils by merging CSVs into a parquet dataset for notebook analysis.
- Pair with
ctrltest-mcpto explore control implications from polar derivatives. - 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
- Fork and
uv pip install --system -e .[dev]. - Run the formatting and test suite.
- Open a PR with before/after polar snippets so reviewers can verify quickly.
MIT license - see LICENSE.
