Microsoft Fabric logoMCP

Governing a Fabric Warehouse from an Agent: the MCP Integration Tools in Practice

An agent has a context window, not a file system. That single constraint shapes how AI-driven governance of a Microsoft Fabric warehouse actually works: the model cannot paste a forty-megabyte INFORMATION_SCHEMA extract into a tool call, cannot hold your SQL credentials, and should not be trusted to invent a schema comparison in its head. What it *can* do is call a small set of typed tools that run the real audit engine server-side and hand back a verdict.

Governing a Fabric Warehouse from an Agent: the MCP Integration Tools in Practice

An agent has a context window, not a file system. That single constraint shapes how AI-driven governance of a Microsoft Fabric warehouse actually works: the model cannot paste a forty-megabyte INFORMATION_SCHEMA extract into a tool call, cannot hold your SQL credentials, and should not be trusted to invent a schema comparison in its head. What it can do is call a small set of typed tools that run the real audit engine server-side and hand back a verdict.

CoreModels exposes five vendor-integration tools over the Model Context Protocol. This is what they take, what they return, and where the sharp edges are. Everything applies equally to plain SQL Server estates, because the fabric connector's artifact contract is ANSI T-SQL INFORMATION_SCHEMA.

Connecting

A deployment serves MCP at two endpoints. The public one carries read-only tools — everything that runs at Viewer role. The admin one adds the write tools.

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

Generic JSON-configured clients want the same two URLs:

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

Both endpoints speak OAuth 2.0 with dynamic client registration and PKCE, so a spec-compliant client discovers the authorization server and completes the flow without a pre-registered client id. Which endpoint a token was minted for is not the security boundary, though. The enforced boundary is the per-project role check: a tool succeeds only when the authenticated user actually holds the tool's required role on the target project — Admin, for both write tools described here.

Every tool takes graphProjectId, validated against the pattern ^[a-f0-9]{32}$, and every schema is closed with additionalProperties: false — a misspelled argument is rejected, never silently ignored.

Discovery: get_vendor_integration_status

Called with only a project id, this lists every registered connector with its capabilities and its artifact contract. Called with a vendor, it answers what that project currently knows about the estate.

{ "graphProjectId": "3f8a91c47b6d42e0a15c9e73d2048b6f", "vendor": "fabric" }
{
  "vendor": "fabric",
  "imported": true,
  "state": {
    "vendor": "fabric",
    "projectName": "wh_sales",
    "importedAt": "2026-02-11T09:14:22.4180000+00:00",
    "sourceFingerprint": "9f3c1a72b48d05e6",
    "counts": "fieldsAdded=0, lineageAdded=0, lineageSkipped=0, nodesEnriched=108",
    "facts": "{\"tables\":\"10\",\"views\":\"2\",\"descriptionsAvailable\":\"false\"}"
  },
  "governedDatasets": 12
}

This is the right first call in any agent workflow. It tells the model whether an import has ever happened, when, against which artifacts, and how much of the estate is governed — before it starts reasoning about drift.

The audit: audit_vendor_project

The centerpiece. It is read-only, runs at Viewer role, and is therefore available on both endpoints.

{
  "graphProjectId": "3f8a91c47b6d42e0a15c9e73d2048b6f",
  "vendor": "fabric",
  "artifacts": {
    "information_schema": "[{\"TABLE_CATALOG\":\"wh_sales\",\"TABLE_SCHEMA\":\"dbo\",\"TABLE_NAME\":\"orders\",\"TABLE_TYPE\":\"BASE TABLE\",\"COLUMN_NAME\":\"order_total\",\"ORDINAL_POSITION\":4,\"DATA_TYPE\":\"nvarchar\",\"IS_NULLABLE\":\"YES\",\"CHARACTER_MAXIMUM_LENGTH\":50}]"
  }
}

Artifacts travel as raw strings, keyed by artifact name — information_schema required, keys optional. The result is a compact JSON payload:

{
  "vendor": "fabric",
  "projectName": "wh_sales",
  "errorCount": 1,
  "warningCount": 2,
  "infoCount": 1,
  "metrics": {
    "Datasets (estate)": "12",
    "Datasets governed": "12 / 12",
    "Fields governed": "96 / 96"
  },
  "findings": [
    { "section": "Drift", "severity": "Error", "code": "field-type-drift",
      "subject": "wh_sales.dbo.orders.order_total",
      "message": "Field type changed since the last import.",
      "detail": "governed: decimal(18,2), estate: nvarchar(50)" }
  ],
  "markdown": "# fabric Schema Audit — wh_sales\n\n🔴 **1 errors · 2 warnings · 1 info** ..."
}

Two things about this payload are worth knowing before an agent reasons over it.

First, the MCP result is deliberately leaner than the HTTP audit response: it carries counts, metrics, findings and markdown, but not the codes map, driftedObjects list or artifact fingerprint that the HTTP surface returns. An agent that needs those should read them from findings, or call the HTTP route.

Second, the payload drops default values to save tokens. A clean audit does not send "errorCount": 0 — it omits the field entirely. Any tool-calling logic that gates on the count must treat a missing errorCount as zero, not as an error condition. It is an easy mistake to make when wiring this tool into an autonomous loop.

Large extracts: the artifactUrls flow

A real warehouse extract is not something a model should be carrying around as a string. Both artifact-bearing tools accept artifactUrls — artifact name to URL — and fetch the content server-side:

{
  "graphProjectId": "3f8a91c47b6d42e0a15c9e73d2048b6f",
  "vendor": "fabric",
  "artifactUrls": {
    "information_schema": "https://artifacts.example.com/wh_sales/information_schema.json",
    "keys": "https://artifacts.example.com/wh_sales/keys.json"
  }
}

Inline and URL forms compose: an agent can inline a small keys file while fetching the large information_schema by URL.

Because these are server-side GETs to caller-supplied addresses, the fetch is guarded. Only https URLs are accepted. Redirects are disabled outright, since a redirect to an internal host would defeat any pre-flight check. Hosts that resolve to loopback, link-local (including cloud metadata), or private ranges are refused. The response is capped at 256 MB, and the request times out after sixty seconds.

Failures are reported rather than thrown. Each problem lands in a fetchProblems array on the result — "Refused to fetch 'keys' from ...: host resolves to a non-public address (10.2.0.14)" — and the call proceeds with whatever artifacts were usable. Only when nothing at all could be assembled does the tool return an error, and it says exactly which arguments it expected.

Writing: import_vendor_project

Import runs at Admin role, so it is served on the admin endpoint only. Its annotations tell an agent what it is dealing with before it commits: destructive is declared false, because import is additive — already-governed datasets are never mutated.

{
  "graphProjectId": "3f8a91c47b6d42e0a15c9e73d2048b6f",
  "vendor": "fabric",
  "artifactUrls": { "information_schema": "https://artifacts.example.com/wh_sales/information_schema.json" }
}
{ "success": true, "vendor": "fabric", "projectName": "wh_sales",
  "datasetsAdded": 12, "fieldsAdded": 96, "nodesEnriched": 108,
  "lossiness": [], "errors": [] }

Note the zero-valued fields are absent here too — datasetsSkippedExisting, lineageEdgesAdded and lineageEdgesSkipped were all zero on this run. The MCP import result also does not carry snapshotStored; to confirm the estate snapshot was persisted for later re-audits, read the HTTP import response or check the status tool afterwards.

Generating: generate_vendor_artifacts

Viewer role, both endpoints, read-only. For Fabric it emits one artifact, coremodels_fabric_tables.sql, containing T-SQL CREATE TABLE statements rebuilt from governed meaning.

{
  "graphProjectId": "3f8a91c47b6d42e0a15c9e73d2048b6f",
  "vendor": "fabric",
  "typeNames": ["customers", "orders"]
}

The schema also declares targetVersion, used by connectors with dialect variants; the Fabric generator does not read it. Views are skipped with an explicit lossiness record, so an agent that summarizes the result can tell the user why a view is missing rather than guessing.

Linking estates: reconcile_vendor_projects

When one CoreModels project governs both a transformation layer and the warehouse it lands in, this tool finds the datasets both estates describe as the same physical relation — matched on database.schema.table — and links each pair with reciprocal sameAs mappings.

{
  "graphProjectId": "3f8a91c47b6d42e0a15c9e73d2048b6f",
  "vendorA": "dbt",
  "vendorB": "fabric"
}

It runs at Admin role, is declared idempotent, and returns the matched pairs plus the unmatched identities on each side — which is usually the more interesting half of the answer.

What MCP does not expose

Three drift-loop verbs have no MCP tool: reaudit, history and the SVG badge. They live on the HTTP surface. An agent that needs the drift trail should call the history route directly; an agent asked "has this changed since we last looked?" should either run a fresh audit or call re-audit over HTTP.

We drew that boundary deliberately. The tools an agent gets are the ones where an autonomous call is safe and the answer is a fact: what is registered, what is governed, what drifted, what the governed model says the DDL should be. Nothing here writes governed meaning without a human holding the role that authorises it.

Whoever runs the extract will want the Microsoft Fabric quickstart, which documents the queries that produce these artifacts.