Salesforce logoMCP

"Did the Org Drift?" — Running Salesforce Governance Through an AI Agent

Watch an agent handle a governance request end to end. A data engineer types: *"Here's this week's describe export — check whether the Salesforce org still matches the governed model, and summarize anything that changed."* The agent calls one tool, reads back structured findings plus a ready-made markdown report, and answers with specifics: which object, which field, which severity. No dashboard visit, no memorized route, no guessing.

"Did the Org Drift?" — Running Salesforce Governance Through an AI Agent

Watch an agent handle a governance request end to end. A data engineer types: "Here's this week's describe export — check whether the Salesforce org still matches the governed model, and summarize anything that changed." The agent calls one tool, reads back structured findings plus a ready-made markdown report, and answers with specifics: which object, which field, which severity. No dashboard visit, no memorized route, no guessing.

That works because CoreModels exposes its vendor-integration layer over MCP (the Model Context Protocol). Any MCP-capable client — Claude, Claude Code, or anything else that speaks the protocol — gets the core governance verbs of the HTTP API, with the same read-only posture and the same per-project role checks. This article covers the four integration tools that matter for Salesforce, with the exact arguments each takes.

Connecting

CoreModels serves two OAuth-protected MCP endpoints:

  • /mcp — the public endpoint. It carries only read-only (Viewer-role) tools.
  • /mcp-admin — the admin endpoint, which adds the write tools.

For Claude Code:

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

then complete the OAuth flow when prompted. If your agent needs to run imports, add the admin endpoint as a second server:

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

Generic JSON-configured clients need only:

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

The OAuth flow supports dynamic client registration and PKCE, so spec-compliant clients handle discovery and authorization without a pre-registered client id. One thing to internalize: the endpoint split is a serving convenience, not the security boundary. The enforced boundary is the per-project role check — a write tool requires Admin membership in the target project no matter which endpoint the token arrived through.

Discovery: get_vendor_integration_status

An agent's opening move. With only a project id, it lists every registered connector, its capabilities, and its artifact contract:

{ "graphProjectId": "3f2b8c41d9e04a7f9c1d5e8b2a604c73" }

From that single call the agent learns — without any hardcoded vendor knowledge — that salesforce supports Import, Audit, Generate and wants one artifact called describe. Add the vendor and the tool switches to project status:

{ "graphProjectId": "3f2b8c41d9e04a7f9c1d5e8b2a604c73", "vendor": "salesforce" }

The result reports whether an import has ever happened (imported), the recorded last-import state (timestamps, artifact fingerprint, counts), and how many governed datasets currently trace to Salesforce. This is how an agent decides its next step instead of asking you.

The audit: audit_vendor_project

The workhorse. It compares vendor artifacts against the governed graph — coverage, drift, conformance — and writes nothing, which is why it runs at Viewer role and is available on the public endpoint. Required arguments are graphProjectId (32-char hex) and vendor; the artifact rides along inline:

{
  "graphProjectId": "3f2b8c41d9e04a7f9c1d5e8b2a604c73",
  "vendor": "salesforce",
  "artifacts": { "describe": "<contents of describe.json>" }
}

The result gives the agent both audiences at once: machine-readable error/warning/info counts and a findings array (section, severity, code, subject, message, detail) for reasoning, plus a complete markdown report it can paste into a pull request or a chat reply verbatim. The contract the agent needs to know is one line: an error count above zero means the org violates governed meaning. For Salesforce, warnings and infos carry the connector's hygiene rules — unrestricted picklists, custom fields without help text, polymorphic lookups — which are exactly the observations an agent can turn into a prioritized cleanup list.

Large orgs: the artifactUrls flow

A describe export for a wide org can be bigger than an MCP client wants to inline into a tool call. For that, every artifact-bearing integration tool accepts artifactUrls — name to URL — and the server fetches the content itself:

{
  "graphProjectId": "3f2b8c41d9e04a7f9c1d5e8b2a604c73",
  "vendor": "salesforce",
  "artifactUrls": { "describe": "https://artifacts.example.com/exports/describe.json" }
}

Because these are server-side requests to caller-supplied URLs, the fetch is deliberately conservative: HTTPS only, redirects disabled, hosts that resolve to loopback, link-local, or private ranges refused, and the response size capped. A URL that fails validation or fetching does not blow up the call — the problem is reported in the result's fetch-problems list, so the agent sees exactly what was refused and why. You can mix the two forms: small artifacts inline, large ones by URL.

The write path: import_vendor_project

Importing an estate is the one governance act that writes, so the tool requires the Admin role and is served on the admin endpoint only. Its arguments mirror the audit exactly — same graphProjectId, vendor, artifacts/artifactUrls, optional spaces — and its behavior mirrors the HTTP import: datasets become Types, fields become Elements, active picklists become Taxonomies, lookups become references, and re-imports are additive, never mutating what is already governed. The result reports the added/skipped/enriched counts plus lossiness and errors, so an agent can state precisely what changed.

A useful pattern follows from the posture: let agents on the public endpoint audit freely and propose, and reserve the admin-endpoint connection for the narrower set of principals allowed to change what is governed. The tools' declared annotations help here too — every read tool is marked read-only, so a cautious MCP client can verify the posture instead of trusting prose.

Closing the loop: generate_vendor_artifacts

The generation tool runs the other direction — governed model out to vendor artifacts — and is still read-only from the graph's perspective (Viewer role, both endpoints):

{ "graphProjectId": "3f2b8c41d9e04a7f9c1d5e8b2a604c73",
  "vendor": "salesforce",
  "typeNames": ["Invoice__c"] }

For Salesforce the result is Metadata-API CustomObject XML, one objects/{ApiName}.object artifact per governed Type: restricted picklist value sets from taxonomies, Lookup fields from governed references, required flags from NotNull checks. The agent hands you a scaffold to review and deploy through your own change process — nothing is pushed to your org, ever.

What a real session looks like

Put together, the loop an agent runs is short and auditable:

  1. get_vendor_integration_status — is Salesforce governed here, and when was it last imported?
  2. audit_vendor_project with the fresh describe (inline or by URL) — what drifted?
  3. Reason over the findings: separate errors (meaning violations) from warnings (hygiene), group by object using the subject fields.
  4. Reply with the markdown report and a recommendation — and, if asked, generate_vendor_artifacts to scaffold the fix.

Two honest boundaries to know. First, there is no live-org tool: CoreModels never holds Salesforce credentials, so the describe export is always produced by you or your pipeline — the agent governs artifacts, not orgs. Second, the drift-trail verbs — re-audit, history, badge — live on the HTTP surface rather than as MCP tools today, so an agent that wants a recorded trail works alongside your CI gate rather than replacing it.

Everything above, including the credential-free extraction recipe that produces describe.json, is collected in the Salesforce quickstart in the CoreModels docs (quickstarts/salesforce).