# Apache Airflow — MCP: Governing Airflow with an AI Agent: The CoreModels MCP Tools in Practice

"Which of our pipelines have no accountable owner, and is anything downstream of a paused DAG?" That is a governance question, and until recently answering it meant a human clicking through the Airflow UI and cross-referencing a wiki. With CoreModels, an AI agent answers it directly: the same import/audit machinery we expose over HTTP is exposed over the Model Context Protocol, so an agent connected to your CoreModels server can inspect connector capabilities, run a full drift-and-hygiene audit of an Airflow deployment, and read back a human-quality report - all through typed tool calls.

# Governing Airflow with an AI Agent: The CoreModels MCP Tools in Practice

"Which of our pipelines have no accountable owner, and is anything downstream of a paused DAG?" That is a governance question, and until recently answering it meant a human clicking through the Airflow UI and cross-referencing a wiki. With CoreModels, an AI agent answers it directly: the same import/audit machinery we expose over HTTP is exposed over the Model Context Protocol, so an agent connected to your CoreModels server can inspect connector capabilities, run a full drift-and-hygiene audit of an Airflow deployment, and read back a human-quality report - all through typed tool calls.

This article shows the exact tools, their exact arguments, and the flow for large payloads. Everything here is the Apache Airflow connector (vendor key `airflow`, capabilities Import and Audit - no Generate, and the tools tell the agent so rather than improvising).

## Connecting an agent

CoreModels serves MCP over stateless streamable HTTP with OAuth 2.0. There are two endpoints with a deliberate split:

- `/mcp` - the public endpoint; read-only tools only (everything that runs at Viewer role).
- `/mcp-admin` - additionally serves the write tools; per-project role checks still apply on every call.

For Claude Code:

```bash
claude mcp add --transport http coremodels https://coremodels.example.com/mcp
```

then complete the OAuth flow (`/mcp` inside Claude Code). For claude.ai or Claude Desktop, add a custom connector pointing at the same URL. Generic MCP clients configure:

```json
{
  "mcpServers": {
    "coremodels": { "type": "http", "url": "https://coremodels.example.com/mcp" }
  }
}
```

Dynamic Client Registration is supported, so no pre-registered client id is needed; unauthenticated requests get a `401` with discovery metadata and a spec-compliant client handles the rest.

The key security property for the rest of this article: the audit tool is read-only and runs at Viewer role, so it is available on the public endpoint. The import tool requires Admin membership on the target project and is served on `/mcp-admin` only. An agent can *observe* governance freely; *changing* the governed graph is a deliberately narrower door.

## Discovery: what can this server govern?

The agent's first call is usually `get_vendor_integration_status`. Without a `vendor` argument it lists every registered connector with capabilities and expected artifacts:

```json
{ "graphProjectId": "3f2a9c81b4de4f6aa1c2d3e4f5a6b7c8" }
```

The Airflow entry in the result carries `"capabilities": "Import, Audit"` and the artifact notes - `dags` required, `tasks` and `datasets` optional - so the agent learns the artifact contract without any documentation lookup. With a `vendor`, the same tool returns the project's last-import state:

```json
{ "graphProjectId": "3f2a9c81b4de4f6aa1c2d3e4f5a6b7c8", "vendor": "airflow" }
```

```json
{ "vendor": "airflow", "imported": true, "state": { "…": "…" }, "governedDatasets": 14 }
```

A result without `imported: true` tells the agent an import has to happen first; `governedDatasets` tells it how much of the estate resolves to governed Types.

## The audit: `audit_vendor_project`

This is the workhorse - the free Schema Audit as a tool call. Required arguments are `graphProjectId` (the 32-character hex project id) and `vendor`; artifacts come inline via `artifacts` or by reference via `artifactUrls`:

```json
{
  "graphProjectId": "3f2a9c81b4de4f6aa1c2d3e4f5a6b7c8",
  "vendor": "airflow",
  "artifacts": {
    "dags": "<the GET /api/v1/dags response>",
    "datasets": "<the GET /api/v1/datasets response>"
  }
}
```

The result gives the agent both machine and human channels: `errorCount`, `warningCount`, `infoCount`, `metrics`, a `findings` array (section, severity, code, subject, message, detail), and `markdown` - the complete report ready to paste into a PR or a chat reply. For Airflow, the findings the agent should expect are the connector's four conformance rules - `dag-no-description` (Info), `dag-no-owner` (Warning), `asset-unproduced` (Warning), `paused-producer` (Warning) - plus the shared coverage and drift codes once the estate and the governed model diverge. `errorCount > 0` is the same CI-gate fail signal the HTTP surface uses, so an agent can act as a release gatekeeper with one comparison.

So the opening question - unowned pipelines, staleness downstream of paused DAGs - is answered by one tool call: `dag-no-owner` findings name the unaccountable DAGs, and `paused-producer` findings name each asset whose every producer is paused while active DAGs still consume it, with the producers and active consumers listed in the finding's `detail`.

## Large deployments: the `artifactUrls` flow

A `dags` response for a big deployment can be far larger than an agent wants to inline in a tool call. For that, every artifact-bearing integration tool accepts `artifactUrls` - artifact name to URL - and the server fetches the content itself:

```json
{
  "graphProjectId": "3f2a9c81b4de4f6aa1c2d3e4f5a6b7c8",
  "vendor": "airflow",
  "artifactUrls": {
    "dags": "https://artifacts.example.com/exports/airflow/dags.json",
    "tasks": "https://artifacts.example.com/exports/airflow/tasks.json",
    "datasets": "https://artifacts.example.com/exports/airflow/datasets.json"
  }
}
```

You can mix the two: small artifacts inline, the big one by URL. These are server-side fetches of caller-supplied URLs, so they are SSRF-guarded, and the guard is worth knowing because refusals are visible to the agent:

- `https` only - any other scheme is refused;
- redirects are disabled (a redirect to an internal host would bypass any pre-flight check);
- hosts resolving to loopback, link-local (cloud metadata), or private ranges are refused;
- responses are size-capped (256 MB) with a 60-second timeout.

Fetch failures do not silently vanish: each one lands in a `fetchProblems` array in the tool result (for example, `Refused to fetch 'dags' from http://…: only https URLs are allowed`), so the agent can tell the difference between "your estate is clean" and "I never saw your estate."

## Importing over MCP: `import_vendor_project`

When status comes back without `imported: true`, the governed model has to be built first. The import tool takes the same arguments as the audit tool - `graphProjectId`, `vendor`, `artifacts` and/or `artifactUrls`, optional `spaces` - but it is an Admin-role tool served only on `/mcp-admin`:

```json
{
  "graphProjectId": "3f2a9c81b4de4f6aa1c2d3e4f5a6b7c8",
  "vendor": "airflow",
  "artifacts": { "dags": "<dags.json>", "tasks": "<tasks.json>", "datasets": "<datasets.json>" }
}
```

The result reports `datasetsAdded`, `datasetsSkippedExisting`, `fieldsAdded`, `lineageEdgesAdded`, `lineageEdgesSkipped`, `nodesEnriched`, plus `lossiness`, `errors`, and `fetchProblems`. Import is additive on re-import - already-governed nodes are never mutated - which makes it safe for an agent to re-run: the way to see what changed is `audit_vendor_project`, not a destructive re-import. That division of labor is stated in the tools' own descriptions, so a well-behaved agent discovers it from `tools/list` alone.

One thing an agent cannot do for Airflow is generate artifacts. The connector's capabilities are Import and Audit, and the capability gate refuses before any emission code runs: `generate_vendor_artifacts` returns an explicit error - "capabilities: Connector 'airflow' does not support generation." The agent already learned the *why* from discovery, where the Airflow entry declares exactly two capabilities. We consider that refusal a feature: an agent told plainly that something is unsupported reasons correctly instead of retrying.

## A governance loop an agent can run

Putting it together, a practical agent playbook for an Airflow estate:

1. `get_vendor_integration_status` with `vendor: "airflow"` - is the estate imported, and how much is governed?
2. If not imported (and the agent has admin access): `import_vendor_project` with the three artifacts.
3. `audit_vendor_project` on fresh artifacts - read `errorCount` for the gate, `findings` for specifics.
4. Summarize from `markdown`, name owners from `dag-no-owner` subjects, flag every `paused-producer` asset with its producers and consumers from `detail`.

Steps 1, 3, and 4 run against the public read-only endpoint with Viewer membership - which means you can hand a governance agent to anyone on the team without handing them write access to the governed model.

The artifact extraction recipe (three `curl` calls against your own Airflow REST API - CoreModels never holds your Airflow credentials) is in the Apache Airflow quickstart in the CoreModels integration docs.
