# Databricks — MCP: Ask Your Agent Whether the Lakehouse Drifted: Unity Catalog Governance over MCP

"Has our Unity Catalog estate drifted from what we agreed it means?" is a question an AI agent can now answer with evidence instead of vibes. The same governance verbs that back the CoreModels HTTP API - status, audit, import, generate - are exposed as tools on our MCP server, so an agent can inspect a governed Databricks estate, audit a fresh extract against it, and draft the fix, all inside one conversation. This article walks that loop exactly as an agent drives it, with the real tool names and arguments, including the `artifactUrls` path for extracts too large to paste into a chat.

# Ask Your Agent Whether the Lakehouse Drifted: Unity Catalog Governance over MCP

"Has our Unity Catalog estate drifted from what we agreed it means?" is a question an AI
agent can now answer with evidence instead of vibes. The same governance verbs that back the
CoreModels HTTP API - status, audit, import, generate - are exposed as tools on our MCP
server, so an agent can inspect a governed Databricks estate, audit a fresh extract against
it, and draft the fix, all inside one conversation. This article walks that loop exactly as
an agent drives it, with the real tool names and arguments, including the `artifactUrls` path
for extracts too large to paste into a chat.

## Two endpoints, one rule

CoreModels serves MCP over streamable HTTP with OAuth 2.0. The public endpoint at
`https://coremodels.example.com/mcp` carries read-only tools - everything that runs at the
Viewer role. The admin endpoint at `/mcp-admin` additionally carries write tools, including
vendor import. The safety property is enforced twice: the public endpoint simply does not
serve write tools, and every tool checks the caller's per-project role regardless of which
endpoint the token came from. An agent connected read-only can observe, audit, and report all
day; it is structurally incapable of changing governed meaning.

Connecting from Claude Code:

```bash
claude mcp add --transport http coremodels https://coremodels.example.com/mcp
# write access, for the import flow:
claude mcp add --transport http coremodels-admin https://coremodels.example.com/mcp-admin
```

then complete the OAuth flow when prompted. Spec-compliant MCP clients handle discovery,
dynamic client registration, and the PKCE authorization-code flow automatically - there is no
client id to pre-provision. Any JSON-configured client works the same way:

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

## Step 1 - Discovery: `get_vendor_integration_status`

A well-behaved agent assumes nothing about the deployment. Called with only a project id,
this tool lists every registered connector with capabilities and expected artifacts:

```json
{ "graphProjectId": "0a1b2c3d4e5f60718293a4b5c6d7e8f9" }
```

Add the vendor and it answers the sharper question - has this project imported a Databricks
estate, and when?

```json
{ "graphProjectId": "0a1b2c3d4e5f60718293a4b5c6d7e8f9", "vendor": "databricks" }
```

The result reports whether an import exists, the recorded last-import state (when it ran, the
artifact fingerprint, a counts summary, and the parser's facts - for Databricks, the table and
view totals), and the number of Unity Catalog identities currently resolvable to governed
Types. From here the agent branches sensibly: no import yet means proposing one; a fingerprint
that differs from the extract in hand means proposing an audit.

## Step 2 - The audit: `audit_vendor_project`

This is the workhorse, and it is Viewer-role, so it lives on both endpoints. It compares
vendor artifacts against the governed graph - coverage, drift, conformance - and never writes
anything. The required arguments are `graphProjectId` (32-char hex) and `vendor`; the
artifacts come inline, as URLs, or both:

```json
{
  "graphProjectId": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "vendor": "databricks",
  "artifacts": {
    "information_schema": "[{\"table_catalog\":\"main\",\"table_schema\":\"sales\",\"table_name\":\"orders\",\"table_type\":\"MANAGED\",\"table_comment\":\"Customer orders.\",\"column_name\":\"order_id\",\"ordinal_position\":1,\"full_data_type\":\"BIGINT\",\"is_nullable\":\"NO\",\"comment\":\"Key.\"}]"
  }
}
```

Note the shape: each artifact value is the raw JSON text of the extract, carried as a string.
For Databricks, `information_schema` is required; `keys` and `lineage` are optional extras
from the documented Unity Catalog queries.

The result carries the error, warning, and info counts, the coded findings with section and
severity, and a ready-to-post Markdown report. The contract an agent should internalize is
the same one CI uses: an error count above zero means the extract violates governed meaning.
Warnings (for Databricks: `key-column-undeclared`) and infos (`table-no-comment`) are
advisory - good material for the agent to turn into a to-do list rather than an alarm.

One honest limitation: the MCP audit tool has no history-recording flag. Appending a run to
the project's rolling audit trail is an HTTP-surface affair; over MCP the audit is purely a
point-in-time read.

## Step 2a - Large estates: the `artifactUrls` flow

A real information_schema extract for a wide schema will not fit comfortably in a
conversation. Instead of inlining, the agent passes URLs and the server fetches them:

```json
{
  "graphProjectId": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "vendor": "databricks",
  "artifactUrls": {
    "information_schema": "https://artifacts.example.com/extracts/information_schema.json",
    "keys": "https://artifacts.example.com/extracts/keys.json",
    "lineage": "https://artifacts.example.com/extracts/lineage.json"
  }
}
```

Because these are server-side fetches of caller-supplied URLs, they are SSRF-guarded: only
`https` is accepted, redirects are disabled, hosts that resolve to loopback, link-local
(cloud metadata), or private ranges are refused, and the response size is capped at 256 MB.
Failed or refused fetches do not silently vanish - they come back in a `FetchProblems` list
in the result, so the agent can tell the difference between "your estate is clean" and "I
could not read half your estate." `artifacts` and `artifactUrls` can be mixed in one call.

## Step 3 - The import: `import_vendor_project` (admin endpoint only)

Import is the one write in the loop: Admin role, served only on `/mcp-admin`. Same argument
shape as the audit - vendor plus artifacts, inline or by URL:

```json
{
  "graphProjectId": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "vendor": "databricks",
  "artifactUrls": {
    "information_schema": "https://artifacts.example.com/extracts/information_schema.json",
    "keys": "https://artifacts.example.com/extracts/keys.json"
  }
}
```

The result reports what was added - datasets, fields, lineage edges, enriched nodes - plus
lossiness and errors. The posture matters more than the counts: import is additive, and
already-governed datasets are never mutated on re-import. That makes it safe to let an agent
run: the worst an over-eager import can do is add new governed material and refresh vendor
bookkeeping, never rewrite meaning. When the agent wants to know what *changed*, the answer
is the audit, not a re-import.

## Step 4 - Closing the loop: `generate_vendor_artifacts`

Viewer role, both endpoints, read-only. It turns governed meaning back into vendor artifacts
- for Databricks, one Delta DDL script:

```json
{
  "graphProjectId": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "vendor": "databricks",
  "typeNames": ["orders", "customers"]
}
```

`typeNames` narrows generation to specific governed types; omit it for everything eligible.
The result contains the generated `coremodels_delta_tables.sql` - `CREATE TABLE IF NOT
EXISTS … USING DELTA` with `NOT NULL` from governed checks, informational key constraints,
and taxonomy allowed values on column comments - plus a lossiness list (views, for instance,
are skipped as derived objects). A good agent pattern: after an audit surfaces
`key-column-undeclared` warnings, generate the DDL and present the constraint declarations as
a reviewable diff for a human to apply in Databricks. The agent drafts; the engineer merges.

## Step 5 - When the lakehouse is governed from two sides: `reconcile_vendor_projects`

Unity Catalog is rarely the only system with an opinion about your tables. If the same project
also governs the transformation tool that materializes into those tables, the fifth integration
tool links the two descriptions of one physical relation, matched on `database.schema.table`:

```json
{
  "graphProjectId": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "vendorA": "databricks",
  "vendorB": "dbt"
}
```

It is Admin-role and served on `/mcp-admin` only, declares `idempotentHint: true`, and returns
`linksWritten` plus the matched pairs and the unmatched leftovers on each side. Run it after
both estates have been imported; re-running after the next import refreshes the links rather
than duplicating them.

## What this adds up to

The five tools compose into a complete conversational governance loop for a lakehouse:
discover what is governed, audit what is real, import what is new, generate what should be
declared, reconcile what two systems describe twice - with the write steps gated behind a
separate endpoint and an Admin role check, and every read available to any Viewer-scoped agent.
Nothing in the loop requires Databricks credentials at any point; the agent handles extracts
your own tooling produced.

For the extraction queries behind those artifacts and the HTTP equivalents of each tool, see
the Databricks quickstart in the CoreModels documentation.
