# How MCP-Airflow-API Handles Dynamic API Version Switching Between Airflow 2.x and 3.0+

> MCP-Airflow-API dynamically switches between Airflow 2.x and 3.x using AIRFLOW_API_VERSION. Adapts URL construction authentication and endpoints at runtime.

- Repository: [JungJungIn/mcp-airflow-api](https://github.com/call518/mcp-airflow-api)
- Tags: how-to-guide
- Published: 2026-02-26

---

**MCP-Airflow-API uses the `AIRFLOW_API_VERSION` environment variable to dynamically switch between Airflow 2.x (v1 API) and Airflow 3.x (v2 API), automatically adapting URL construction, authentication methods, and endpoint selection at runtime.**

The `call518/mcp-airflow-api` repository provides a Model Context Protocol (MCP) server that abstracts Airflow's REST API differences across major versions. By implementing a runtime detection strategy, the library eliminates the need for separate codebases while supporting both legacy Airflow 2.x installations and modern Airflow 3.0+ deployments.

## Runtime Version Detection

### Environment Variable Configuration

The version detection logic resides in [`src/mcp_airflow_api/functions.py`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/functions.py). The `get_api_version()` function reads the `AIRFLOW_API_VERSION` environment variable and normalizes it to lowercase:

```python

# src/mcp_airflow_api/functions.py

def get_api_version():
    """Get API version from environment."""
    return os.getenv("AIRFLOW_API_VERSION", "v1").lower()

```

If the environment variable is unset, the system defaults to **v1**, ensuring backward compatibility with existing Airflow 2.x deployments.

## Dynamic URL Construction

All HTTP requests route through `construct_api_url()`, which injects the detected API version into the request path. This function ensures that endpoints are correctly prefixed regardless of which Airflow version is targeted:

```python

# src/mcp_airflow_api/functions.py

def construct_api_url(path: str) -> str:
    base_url = get_api_base_url()
    version   = get_api_version()
    ...
    return f"{base_url}/{version}{path}"

```

A request to `"/dags"` resolves to:
- `http://<host>/api/v1/dags` when `AIRFLOW_API_VERSION=v1` (Airflow 2.x)
- `http://<host>/api/v2/dags` when `AIRFLOW_API_VERSION=v2` (Airflow 3.x)

## Adaptive Authentication Strategies

The `airflow_request()` function in [`src/mcp_airflow_api/functions.py`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/functions.py) implements version-specific authentication logic to handle the transition from Basic Auth in Airflow 2.x to JWT-based authentication in Airflow 3.x.

### Basic Auth for Airflow 2.x (v1)

When `get_api_version()` returns `"v1"`, the system uses `aiohttp.BasicAuth` with the configured username and password:

```python
auth = aiohttp.BasicAuth(username, password)

```

### JWT Token Auth for Airflow 3.x (v2)

For Airflow 3.x compatibility, the v2 API path attempts to retrieve a JWT token via `get_jwt_token()`. The token is cached internally (`_jwt_token`) for approximately 23 hours to minimize authentication overhead. If JWT retrieval fails, the system gracefully falls back to Basic Auth:

```python

# src/mcp_airflow_api/functions.py (excerpt)

if api_version == "v2":
    try:
        jwt_token = await get_jwt_token()
        headers["Authorization"] = f"Bearer {jwt_token}"
    except Exception:
        auth = aiohttp.BasicAuth(username, password)   # fallback

elif api_version == "v1":
    auth = aiohttp.BasicAuth(username, password)

```

## Tool Registration Architecture

MCP-Airflow-API implements a shared tool pattern that avoids code duplication while supporting both API versions through dependency injection.

### Shared Implementation Pattern

The 43 core MCP tools—including `list_dags`, `trigger_dag`, and `get_health`—are implemented once in [`src/mcp_airflow_api/tools/common_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/tools/common_tools.py). These tools reference a module-level `airflow_request` variable that is injected at registration time.

### Version-Specific Registration Modules

The [`v1_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/v1_tools.py) and [`v2_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/v2_tools.py) modules handle version-specific registration by binding the appropriate request function:

```python

# src/mcp_airflow_api/tools/v1_tools.py

from ..functions import airflow_request as airflow_request_v1
...
common_tools.airflow_request = airflow_request_v1
common_tools.register_common_tools(mcp)

```

```python

# src/mcp_airflow_api/tools/v2_tools.py

from ..functions import airflow_request as airflow_request_v2
...
common_tools.airflow_request = airflow_request_v2
common_tools.register_common_tools(mcp)

```

This architecture ensures that the same business logic executes against the correct API version without maintaining separate tool implementations.

## Version-Aware Endpoint Selection

Even within shared tools, specific endpoints vary between Airflow versions. The `get_health` tool in [`common_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/common_tools.py) demonstrates runtime endpoint selection:

```python

# src/mcp_airflow_api/tools/common_tools.py

from ..functions import get_api_version
...
if get_api_version() == "v2":
    resp = await airflow_request("GET", "/monitor/health")   # Airflow 3.x

else:
    resp = await airflow_request("GET", "/health")          # Airflow 2.x

```

Similarly, Airflow 2.x-specific features such as user/role management and the dataset API include version guards that return explicit "not available" messages when `v2` is active, preventing errors against non-existent endpoints.

## Runtime Configuration Examples

### Environment Setup

Configure the target Airflow version using environment variables before initializing the MCP server:

```bash

# Use Airflow 2.x (v1 API)

export AIRFLOW_API_VERSION=v1
export AIRFLOW_API_BASE_URL=http://localhost:8080/api
export AIRFLOW_API_USERNAME=admin
export AIRFLOW_API_PASSWORD=admin

# Use Airflow 3.x (v2 API)

export AIRFLOW_API_VERSION=v2

```

### Python Implementation

The same Python code works across both versions:

```python
from mcp_airflow_api.mcp_main import MCP

mcp = MCP()
await mcp.register_tools()           # registers the correct set based on AIRFLOW_API_VERSION

dags = await mcp.tools.list_dags(limit=5)   # Calls the shared implementation

print(dags)

```

### Direct Version-Specific Invocation

For scenarios requiring explicit version control, use the wrapper functions:

```python
from mcp_airflow_api.functions import airflow_request_v2

# Force a v2 call regardless of the global setting

resp = await airflow_request_v2("GET", "/dags")
print(resp.json())

```

## Summary

MCP-Airflow-API achieves seamless dynamic API version switching through a layered architecture:

- **Environment-driven detection** via `AIRFLOW_API_VERSION` with a safe default to v1 for backward compatibility
- **Dynamic URL construction** that injects the correct version prefix into all API endpoints
- **Adaptive authentication** that selects Basic Auth for Airflow 2.x and JWT tokens (with Basic fallback) for Airflow 3.x
- **Shared tool implementation** with dependency injection, avoiding code duplication while supporting both versions through [`v1_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/v1_tools.py) and [`v2_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/v2_tools.py)
- **Runtime endpoint selection** within tools to handle version-specific paths like `/health` vs `/monitor/health`

This design allows operators to upgrade from Airflow 2.x to 3.x without modifying client code, changing only the `AIRFLOW_API_VERSION` environment variable.

## Frequently Asked Questions

### How do I configure MCP-Airflow-API for Airflow 3.x?

Set the `AIRFLOW_API_VERSION` environment variable to `v2` before starting your application. The library will automatically use JWT authentication and v2 API endpoints. If you are running Airflow 2.x, either leave this unset or explicitly set it to `v1` to use Basic Auth against the v1 API.

### What authentication method does MCP-Airflow-API use for Airflow 2.x?

For Airflow 2.x (v1 API), MCP-Airflow-API uses **Basic Authentication** with username and password credentials configured via environment variables. This is implemented in [`src/mcp_airflow_api/functions.py`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/functions.py) using `aiohttp.BasicAuth`.

### Can I use both API versions simultaneously in the same application?

Yes, through the version-specific wrapper functions `airflow_request_v1()` and `airflow_request_v2()` defined in [`src/mcp_airflow_api/functions.py`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/functions.py). These functions temporarily override the global `AIRFLOW_API_VERSION` environment variable for a single request, allowing you to query both Airflow 2.x and 3.x instances from the same process.

### Where are the version-specific tool registrations defined?

Version-specific registrations are located in [`src/mcp_airflow_api/tools/v1_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/tools/v1_tools.py) for Airflow 2.x and [`src/mcp_airflow_api/tools/v2_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/tools/v2_tools.py) for Airflow 3.x. Both modules import the shared tool implementations from [`common_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/common_tools.py) and inject the appropriate `airflow_request` function before registering tools with the MCP server.