Neo4j logoMCP

Four Tools, One Knowledge Graph: Neo4j Governance from the Agent Side

An AI agent connected to CoreModels over MCP sees a small, deliberately shaped set of vendor-integration tools. Three of them cannot write anything at all; the fourth requires Admin membership on the project *and* a different endpoint. That shape is the point of this article: an agent can drive the entire Neo4j governance loop — discover, audit, report, generate the fix — while the only mutating step in the loop stays behind an explicit privilege boundary.

Four Tools, One Knowledge Graph: Neo4j Governance from the Agent Side

An AI agent connected to CoreModels over MCP sees a small, deliberately shaped set of vendor-integration tools. Three of them cannot write anything at all; the fourth requires Admin membership on the project and a different endpoint. That shape is the point of this article: an agent can drive the entire Neo4j governance loop — discover, audit, report, generate the fix — while the only mutating step in the loop stays behind an explicit privilege boundary.

This matters more for Neo4j than for most estates. Neo4j is where a lot of teams run retrieval for their AI systems, which means the same agent that queries the graph can now check whether the graph still means what the business governed it to mean, over the same protocol.

Connecting

CoreModels serves MCP over streamable HTTP with OAuth 2.0. The public endpoint at /mcp carries the read-only, Viewer-role tools; the write tools live on a separate admin endpoint. Dynamic Client Registration and PKCE are supported, so a spec-compliant client needs no pre-registered client id — point it at the URL and complete the browser flow.

From Claude Code:

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

From a generic JSON-configured client:

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

Authorization is enforced per project and per tool. Read-only tools require Viewer membership; write tools require Editor or Admin membership regardless of which endpoint the token was minted for. Every tool also declares its read-only and open-world hints in tools/list, so a well-built client can show the user which calls can change something before it lets the model make them.

Two conventions apply to every integration tool below: graphProjectId must match ^[a-f0-9]{32}$, and the argument schemas are closed (additionalProperties: false), so a hallucinated argument name is rejected by the schema rather than silently ignored.

Call 1 — get_vendor_integration_status: never hard-code the contract

An agent should not assume what a connector expects; it can ask. With only a project id, the tool lists every registered connector with its capabilities and artifact notes:

{ "graphProjectId": "0f3a9c81d2e6478bbf51a7c3949e02dd" }

The Neo4j entry that comes back declares Import, Audit, Generate and names its two artifacts — meta_schema (required, the value of CALL apoc.meta.schema()) and constraints (optional, SHOW CONSTRAINTS rows). Add the vendor key and the same tool answers a different question: what does this project already know about this estate?

{ "graphProjectId": "0f3a9c81d2e6478bbf51a7c3949e02dd", "vendor": "neo4j" }

The result reports whether an import has happened, the recorded state (when it ran, the artifact fingerprint, counts, parser facts), and how many imported identities currently resolve to governed Types. This is the cheap pre-flight: imported: false means there is nothing to drift-check yet, and the agent should say so instead of running an audit that reports every label as unmapped.

Call 2 — audit_vendor_project: the read-only workhorse

The audit compares fresh Neo4j artifacts against the governed graph and writes nothing. Viewer role, so it is available on the public endpoint:

{
  "graphProjectId": "0f3a9c81d2e6478bbf51a7c3949e02dd",
  "vendor": "neo4j",
  "artifacts": {
    "meta_schema": "{\"Person\":{\"type\":\"node\",\"count\":1200,\"properties\":{\"email\":{\"type\":\"STRING\",\"unique\":true}},\"relationships\":{\"WORKS_FOR\":{\"direction\":\"out\",\"labels\":[\"Company\"]}}},\"Company\":{\"type\":\"node\",\"count\":200,\"properties\":{\"name\":{\"type\":\"STRING\"}}}}",
    "constraints": "[{\"type\":\"UNIQUENESS\",\"labelsOrTypes\":[\"Person\"],\"properties\":[\"email\"]}]"
  },
  "spaces": []
}

The result gives an agent everything it needs to decide what to do next:

  • errorCount, warningCount, infoCount — the counters. errorCount > 0 is the fail signal: governed meaning is violated.
  • metrics — the headline measurements, including how many of the estate's datasets and fields are governed and what share of governed nodes carry a canonical (non-vendor) mapping.
  • findings — each with section (Coverage, Drift, Conformance), severity (Error, Warning, Info), a stable kebab-case code, the subject, a message, and optional detail.
  • markdown — the same report rendered for humans, ready to paste into a chat reply or a pull-request comment.
  • fetchProblems — anything the server could not retrieve when you used URLs (see below).

The codes are the agent's decision table. field-type-drift in the Drift section means a governed property's type moved in the estate — escalate. label-no-unique-id in the Conformance section means a label has no uniqueness constraint, so MERGE-based ingestion can create duplicates silently — suggest a constraint, and, as we will see, generate it. polymorphic-relationship is informational: a relationship targets several labels and only the first is governed as a reference.

Worth knowing precisely: the MCP result is a focused subset of what the HTTP audit returns. It carries the counts, metrics, findings, and markdown; the aggregated codes map, driftedObjects, artifact fingerprint, and lossiness ledger are on the HTTP responses. An agent that needs the fingerprint to compare two runs should read the history over HTTP or count codes from findings itself.

The artifactUrls flow

A meta_schema export from a production knowledge graph can be far too large to inline in a tool call. Every artifact-bearing integration tool therefore accepts a parallel argument that hands the server a URL instead:

{
  "graphProjectId": "0f3a9c81d2e6478bbf51a7c3949e02dd",
  "vendor": "neo4j",
  "artifactUrls": {
    "meta_schema": "https://artifacts.example.com/exports/neo4j/2026-08-04/meta_schema.json",
    "constraints": "https://artifacts.example.com/exports/neo4j/2026-08-04/constraints.json"
  }
}

You can mix the two freely — small payloads inline under artifacts, large ones by reference under artifactUrls — and both are merged into one artifact set before parsing.

Because these are server-side fetches of caller-supplied URLs, they are guarded: https only, automatic redirects disabled (a redirect to an internal host would defeat any pre-flight check), hosts that resolve to loopback, link-local, or private ranges refused, a 60-second timeout, and a hard response-size cap. A URL that fails validation or fetching does not abort the call — the reason is recorded per artifact in fetchProblems and the tool proceeds with whatever it could assemble. Only when nothing usable arrives does the tool return an error explaining that it needs artifacts and/or artifactUrls.

This matters for agent design: an agent should always read fetchProblems, because "the audit ran on one of two artifacts" is a materially different result from "the audit ran on both" — dropping constraints means uniqueness and existence facts fall back to APOC's sampled flags.

Call 3 — import_vendor_project: the one write

Import takes the same argument shape — graphProjectId, vendor, artifacts and/or artifactUrls, optional spaces — and writes the estate into the governed graph: labels become Types, properties become Elements, outgoing typed relationships become references, and Neo4j metadata rides along on the governed nodes. It requires the Admin role and is served on the admin endpoint only; the public /mcp endpoint does not expose it at all.

Two properties make this a tool you can let an agent run without holding your breath. First, import is additive: already-governed nodes are never mutated, so a re-run cannot destroy meaning — the tool's own description points agents at the audit to see what changed instead. Second, the outcome is fully accounted for: counts for datasets, fields, and enriched nodes, a lossiness ledger for anything approximated, and errors reserved for what genuinely could not proceed.

Call 4 — generate_vendor_artifacts: hand back the fix

Generation is read-only (Viewer role, public endpoint) and turns governed meaning into Neo4j enforcement:

{
  "graphProjectId": "0f3a9c81d2e6478bbf51a7c3949e02dd",
  "vendor": "neo4j",
  "typeNames": ["Person", "Company"],
  "spaces": []
}

typeNames narrows the output to named governed types; omit it to cover everything eligible. The result contains one artifact — coremodels_constraints.cypher, kind cypher — with idempotent CREATE CONSTRAINT … IF NOT EXISTS statements for governed uniqueness and existence checks, plus indexes for taxonomy-constrained properties. The agent that flagged label-no-unique-id two calls ago can produce the exact remediation script in the next call and attach it to a pull request. Meaning still changes in exactly one place: CoreModels authors it, Neo4j enforces it.

There is a fifth integration tool, reconcile_vendor_projects (Admin, admin endpoint), which links datasets two vendor estates govern as the same physical relation. It is relevant when a Neo4j graph is loaded from a warehouse whose tables are named identically to your labels.

The loop, end to end

A nightly governance agent for a Neo4j-backed knowledge graph needs four calls and no database credentials:

  1. get_vendor_integration_status — is the estate imported, and what was its last-known state?
  2. audit_vendor_project with artifactUrls pointing at last night's export — did anything drift?
  3. Read fetchProblems, then the counters. On errorCount > 0, escalate with the markdown report; on warnings, summarize the conformance findings.
  4. Optionally generate_vendor_artifacts — attach the current constraint script, so the fix travels with the report.

Where the boundary sits

Three of the drift-loop verbs in the HTTP surface have no MCP equivalent today: re-audit, history, and the status badge. That is deliberate rather than accidental — they are the recording side of the drift loop, and an agent that can silently write history entries is an agent that can quietly make a badge look green. If your agent needs the trail, have it read the history route over HTTP with a Viewer credential.

Everything else holds: every result is machine-readable, every call is role-checked, the only mutating tool sits behind Admin membership on a separate endpoint, and the audit — the call an agent will make hundreds of times — cannot write anything at all.

For the artifact extraction recipe and the HTTP equivalents of these tools, see the Neo4j quickstart in the CoreModels integration docs.