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:
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:
{ "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:
{ "graphProjectId": "0a1b2c3d4e5f60718293a4b5c6d7e8f9" }
Add the vendor and it answers the sharper question — has this project imported a Databricks estate, and when?
{ "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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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.