airflow-dev-mcp
An MCP server that lets an AI coding assistant(Claude Code, Cursor, and other MCP clients) drive a development or local Airflowcluster through its REST API — trigger DAG runs, watch their status, read task logs,and diagnose parse errors, without leaving your editor.
It talks to Airflow over HTTP only. There's no dependency on your Airflow source tree,no filesystem access, and no local config files — everything is set through environmentvariables. It works against both Airflow 3.x (/api/v2, the default) andAirflow 2.x (/api/v1).
Meant for dev/local clusters. It's designed for the write-a-DAG / run-it / read-the-logsloop against a throwaway environment. Pointing it at a production cluster is not recommended.
Install & run
The package ships a single console command, airflow-dev-mcp, which starts the MCPserver on stdio. You rarely run it by hand — your MCP client launches it for you (seebelow). To try it directly, the zero-install option is uv:
uvx airflow-dev-mcp --check # fetch + run a one-shot connectivity check
Or install it as a persistent tool:
uv tool install airflow-dev-mcp
# or
pipx install airflow-dev-mcp
Configure your MCP client
Claude Code
Add the server to ~/.claude.json (applies everywhere) or a project's.claude/settings.json (just that project):
{
"mcpServers": {
"airflow-dev": {
"command": "uvx",
"args": ["airflow-dev-mcp"],
"env": {
"AIRFLOW_URL": "http://localhost:8081",
"AIRFLOW_USERNAME": "admin",
"AIRFLOW_PASSWORD": "admin"
}
}
}
}
Using uvx means you don't have to manage a virtualenv — it fetches and caches thepackage on first launch. If you'd rather pin an installed copy, replace the command with"command": "airflow-dev-mcp", "args": [] after uv tool install.
Restart Claude Code. The tools show up namespaced as mcp__airflow-dev__trigger_dag,and so on.
Other MCP clients
Any client that launches stdio MCP servers works the same way: run the commandairflow-dev-mcp (or uvx airflow-dev-mcp) with the environment variables below.
Configuration
All configuration is via environment variables:
| Variable | Default | Description |
|---|---|---|
AIRFLOW_URL |
http://localhost:8080 |
Base URL of the cluster, no path. |
AIRFLOW_API_PREFIX |
/api/v2 |
API path prefix. Use /api/v1 for Airflow 2.x. |
AIRFLOW_USERNAME |
— | Username. Used together with AIRFLOW_PASSWORD. |
AIRFLOW_PASSWORD |
— | Password. |
AIRFLOW_TOKEN |
— | Explicit bearer token; skips username/password entirely. |
AIRFLOW_AUTH_MODE |
auto |
auto, jwt, or basic (see below). |
AIRFLOW_TOKEN_ENDPOINT |
/auth/token |
Path used to exchange credentials for a JWT. |
AIRFLOW_TIMEOUT |
30 |
HTTP timeout, in seconds. |
AIRFLOW_VERIFY_SSL |
true |
Set false to skip TLS verification (self-signed dev certs). |
Authentication
- Airflow 3.x (the default local/MWAA-style image): leave
AIRFLOW_AUTH_MODE=auto.The server posts your username/password to/auth/token, caches the returned JWT, andsends it as a bearer token on every request. - Airflow 2.x: set
AIRFLOW_API_PREFIX=/api/v1andAIRFLOW_AUTH_MODE=basic(2.xuses HTTP basic auth against the REST API). - Pre-issued token: set
AIRFLOW_TOKENand omit the username/password.
Tools
| Tool | What it does |
|---|---|
trigger_dag |
Start a manual DAG run, optionally with a conf payload. Returns the dag_run_id. |
get_run_status |
State of a run plus per-task states (task, state, try number, operator, timing). |
get_task_logs |
Logs for one task attempt, tailed to the last N lines by default. |
list_dag_runs |
Recent runs of a DAG — find a run when you don't already have its id. |
clear_task_instances |
Clear tasks so they re-run. Defaults to a dry-run preview. |
list_dags |
Registered DAGs with their paused / import-error / active flags. |
get_import_errors |
Parse failures with filename and traceback — why a new DAG isn't showing up. |
set_dag_paused |
Pause or unpause a DAG (new local DAGs start paused). |
list_variables |
Read Airflow Variables (read-only). |
list_connections |
Read Airflow Connections, minus passwords (read-only). |
The four list_* tools, get_run_status, get_task_logs, and get_import_errors arestrictly read-only. trigger_dag, set_dag_paused, and clear_task_instances changecluster state. There are deliberately no tools that create or modify Variables orConnections — that's cluster administration, out of scope for a DAG-development helper.
A typical loop
- Write or edit a DAG file; Airflow re-parses it.
list_dagsto confirm it registered — orget_import_errorsif it didn't.set_dag_paused(dag_id, paused=false)to enable it (new DAGs start paused).trigger_dag(dag_id, conf={...}), note the returneddag_run_id.get_run_status(dag_id, run_id)until it finishes.- On failure,
get_task_logs(...); fix the code, thenclear_task_instances(dag_id, dag_run_id, dry_run=false)to re-run just the affected tasks.
Approving tools once (Claude Code)
Because each capability is its own MCP tool, Claude Code can remember your approvalper tool — unlike shell curl calls, which re-prompt whenever the command stringchanges. When a tool first runs, choosing "don't ask again" persists an allow rule.You can also pre-approve tools in settings so they never prompt.
A reasonable split is to allow the read-only tools and let the state-changing ones prompt.In .claude/settings.json (project) or ~/.claude/settings.json (global):
{
"permissions": {
"allow": [
"mcp__airflow-dev__list_dags",
"mcp__airflow-dev__get_run_status",
"mcp__airflow-dev__get_task_logs",
"mcp__airflow-dev__get_import_errors",
"mcp__airflow-dev__list_dag_runs",
"mcp__airflow-dev__list_variables",
"mcp__airflow-dev__list_connections"
]
}
}
Leaving trigger_dag, set_dag_paused, and clear_task_instances off the list meansthey still ask before acting.
Verifying your setup
Before wiring it into an editor, confirm the URL and credentials work end-to-end:
AIRFLOW_URL=http://localhost:8081 \
AIRFLOW_USERNAME=admin AIRFLOW_PASSWORD=admin \
uvx airflow-dev-mcp --check
It prints OK — … with the DAG count on success, or FAIL — … with the reason(wrong URL, auth failure, or an /api/v1 vs /api/v2 mismatch).
Releasing (maintainers)
Releases publish to PyPI automatically via Trusted Publishing (OIDC) — no APItoken is stored in the repo or in GitHub secrets. The.github/workflows/publish.yml workflow builds anduploads whenever a GitHub Release is published.
One-time setup on PyPI (before the first release):
- Go to https://pypi.org/manage/account/publishing/ and add a pending publisher(works even though the project doesn't exist on PyPI yet):
- PyPI Project Name:
airflow-dev-mcp - Owner:
BrianLondon· Repository:airflow-dev-mcp - Workflow name:
publish.yml - Environment name:
pypi
- PyPI Project Name:
- In the GitHub repo, create an Environment named
pypi(Settings → Environments). Optionally add required reviewers there to gateuploads behind a manual approval.
To cut a release:
- Bump
__version__insrc/airflow_dev_mcp/__init__.pyand commit. - Tag and push:
git tag v0.2.0 && git push origin main --tags. - Create a GitHub Release for that tag. The workflow builds, runs
twine check,and publishes to PyPI.
License
MIT — see LICENSE.