AWS Glue logoMCP

Handing the Glue Catalog to an Agent: CoreModels over MCP

An agent that can answer *"did our lake drift?"* needs three things: a way to reach the governed model, a way to hand it a fresh catalog export, and a guarantee that asking the question cannot change the answer. The CoreModels MCP server provides all three. The same vendor-integration engine behind the HTTP routes is exposed as MCP tools, with the read verbs at Viewer role and the write verbs behind an admin endpoint and real project membership.

Handing the Glue Catalog to an Agent: CoreModels over MCP

An agent that can answer "did our lake drift?" needs three things: a way to reach the governed model, a way to hand it a fresh catalog export, and a guarantee that asking the question cannot change the answer. The CoreModels MCP server provides all three. The same vendor-integration engine behind the HTTP routes is exposed as MCP tools, with the read verbs at Viewer role and the write verbs behind an admin endpoint and real project membership.

This article is the agent-side of AWS Glue governance: the tools, their exact argument names, the flow for catalogs too large to paste into a conversation, and the response quirks worth teaching your agent about.

Connect

The server speaks stateless streamable HTTP with OAuth 2.0. Two endpoints:

  • /mcp — the public endpoint. Only Viewer-role tools are listed or executable here.
  • /mcp-admin — additionally carries the write tools, including vendor import.

From Claude Code:

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

then run /mcp inside the session to complete authorization. For a JSON-configured client:

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

Authorization is discovery-driven: unauthenticated requests answer 401 with a WWW-Authenticate header pointing at the protected-resource metadata, and spec-compliant clients handle dynamic client registration and the PKCE authorization-code flow without a pre-registered client id. Point a second connection at /mcp-admin only when the agent genuinely needs to import. The endpoint split is convenience; the enforced boundary is the per-project role check, so a Viewer-scoped identity on the admin endpoint still cannot write.

The five tools

ToolRoleEndpointWhat it does for Glue
get_vendor_integration_statusViewerbothlists connectors, or reports the project's last Glue import
audit_vendor_projectViewerbothaudits a get-tables export against the governed model
generate_vendor_artifactsViewerbothemits Athena/Hive CREATE EXTERNAL TABLE DDL from the model
import_vendor_projectAdmin/mcp-adminimports the catalog into the graph, additively
reconcile_vendor_projectsAdmin/mcp-adminlinks datasets two estates govern as the same physical relation

The tools are vendor-neutral — their descriptions use dbt as the worked example — and the vendor argument is what selects Glue. Every schema sets additionalProperties: false, so an agent that invents an argument gets a validation failure rather than a silently ignored field, and graphProjectId is pattern-checked against ^[a-f0-9]{32}$.

Start with discovery

{ "graphProjectId": "9f2c41ab7e0d4c6fa1b83e57c02d9e14" }

With no vendor, get_vendor_integration_status returns every registered connector with its key, display name, capabilities and artifact notes. That is how an agent learns — with no prior knowledge and no hard-coded list — that Glue exists, is keyed glue, supports Import, Audit and Generate, and wants one artifact called tables.

Add the vendor and the same tool switches to project state:

{ "graphProjectId": "9f2c41ab7e0d4c6fa1b83e57c02d9e14", "vendor": "glue" }
{
  "vendor": "glue",
  "imported": true,
  "state": {
    "vendor": "glue",
    "projectName": "lake",
    "importedAt": "2026-08-04T09:14:22.1043117+00:00",
    "sourceFingerprint": "3f9c1d5a7b2e4086",
    "counts": "fieldsAdded=0, lineageAdded=0, lineageSkipped=0, nodesEnriched=10",
    "facts": "{\"tables\":\"2\",\"views\":\"0\"}"
  },
  "governedDatasets": 2
}

A well-prompted agent checks this first. Auditing a project that was never imported produces a wall of dataset-unmapped coverage findings, and the honest answer in that case is "nothing is governed yet", not "your lake is broken".

Audit with inline artifacts

{
  "graphProjectId": "9f2c41ab7e0d4c6fa1b83e57c02d9e14",
  "vendor": "glue",
  "artifacts": { "tables": "{\"TableList\": [ … ]}" }
}

graphProjectId and vendor are the required arguments; artifacts, artifactUrls and spaces are optional, but at least one of the two artifact channels must yield content. The tool runs at Viewer role precisely because auditing writes nothing — there is no recordHistory switch on the MCP surface at all.

A clean run over a small estate comes back like this:

{
  "vendor": "glue",
  "projectName": "lake",
  "warningCount": 1,
  "infoCount": 2,
  "metrics": {
    "Datasets (estate)": "2",
    "Datasets governed": "2 / 2",
    "Fields governed": "8 / 8"
  },
  "findings": [
    {
      "section": "Conformance",
      "severity": "Warning",
      "code": "semi-structured-column",
      "subject": "lake.events",
      "message": "1 struct/map/array column(s) carry ungoverned inner schemas.",
      "detail": "payload"
    }
  ],
  "markdown": "# glue Schema Audit — lake\n…",
  "fetchProblems": []
}

Teach your agent one serialization rule: MCP responses are compact, and default values are dropped. errorCount is absent from the payload above because it was zero — a missing count means zero, not unknown. The same applies to a finding's detail. Reason over findings and markdown; treat errorCount > 0 as the violation signal exactly as CI does.

Large catalogs: the artifactUrls flow

A get-tables export for a real lake can run to many megabytes, which no conversation should carry. Both artifact-bearing tools accept artifactUrls — artifact name to URL — and the server fetches the content itself:

{
  "graphProjectId": "9f2c41ab7e0d4c6fa1b83e57c02d9e14",
  "vendor": "glue",
  "artifactUrls": { "tables": "https://artifacts.example.com/exports/glue/tables.json" },
  "spaces": []
}

You can mix the two channels — inline one artifact, fetch another — though Glue only defines one. The fetch is deliberately narrow, because a server-side GET to a caller-supplied URL is a classic SSRF vector:

  • https only. Anything else is refused before a socket opens.
  • Redirects disabled. A redirect into an internal host would defeat any pre-flight check.
  • Address filtering. Hosts resolving to loopback, link-local (including the cloud metadata range), or private ranges are refused by resolved address, not by hostname string matching.
  • Size capped, with a fetch timeout.

Refusals and failures do not crash the call. They arrive as strings in fetchProblems, e.g. Refused to fetch 'tables' from https://…: host resolves to a non-public address (10.0.3.14) — so the agent can tell you precisely why the audit ran on less than you thought, or why it could not run at all.

Practically: put the export where the server can reach it over https — a pre-signed object URL works well, and expiring links keep the exposure short — and pass the link instead of the bytes.

Import, on the admin endpoint

{
  "graphProjectId": "9f2c41ab7e0d4c6fa1b83e57c02d9e14",
  "vendor": "glue",
  "artifactUrls": { "tables": "https://artifacts.example.com/exports/glue/tables.json" }
}

import_vendor_project takes the same arguments as the audit tool and requires Admin membership. It is declared non-destructive, and that is structural rather than aspirational: tables become Types, columns and partition keys become Elements, vendor metadata is written or refreshed, and already-governed nodes are never mutated. The worst a repeated import can do is add. The result mirrors the HTTP verb — datasets added, fields added, nodes enriched, plus lossiness, errors and the same fetchProblems channel.

This is the property that makes agent-initiated import tolerable at all. An agent can bring new lake tables under governance; it cannot quietly rewrite the meaning of the ones already there. Changed meaning surfaces as audit findings, where a human decides.

Generation closes the loop

{
  "graphProjectId": "9f2c41ab7e0d4c6fa1b83e57c02d9e14",
  "vendor": "glue",
  "typeNames": ["events"]
}

generate_vendor_artifacts runs at Viewer role because it only reads. For Glue it returns one artifact — coremodels_glue_tables.sql, kind sql — containing CREATE EXTERNAL TABLE DDL with governed descriptions and taxonomy allowed-values on column COMMENTs, partition keys in PARTITIONED BY, and a placeholder LOCATION. An agent asked "show me the DDL our governed model implies for the events table" answers with exactly that, and the typeNames filter keeps the reply to the table in question. The tool also accepts targetVersion for connectors with dialect switches; the Glue generator has none.

What the MCP surface does not carry

Three governance verbs are HTTP-only: reaudit, history and badge. An agent cannot replay the stored snapshot, read the rolling trail, or fetch the status SVG through MCP today. If your workflow needs "has this been getting worse?", point the agent at those routes through whatever HTTP capability it has, or run them from your pipeline.

The trade is a deliberate one. The MCP toolkit is the investigative surface: discover what is connected, audit what is there, generate what the model implies, import when authorised. Every read tool declares itself read-only, artifact fetching is guarded and reports its failures as data, and the findings your agent reasons over carry the same stable kebab-case codes your CI gate enforces — one governance engine, two consumers, no second opinion.

The AWS Glue quickstart in the CoreModels documentation lists these tool calls next to their HTTP equivalents if you want both surfaces side by side.