Agent-Driven ShEx Conversion with the transform_schema MCP Tool
"The ontology team published ShEx shapes for the product catalog. Give me the JSON Schema our validation service needs, and tell me exactly what didn't survive the trip."
Agent-Driven ShEx Conversion with the transform_schema MCP Tool
"The ontology team published ShEx shapes for the product catalog. Give me the JSON Schema our validation service needs, and tell me exactly what didn't survive the trip."
That request — pasted into an AI assistant connected to CoreModels — is a one-tool-call job. CoreModels, by ARAMAI, ships a Model Context Protocol (MCP) server whose transform_schema tool runs the full mapping engine statelessly: decode the source format, produce a plan, validate it through the universal gate, execute deterministically, encode the target. ShEx (format key shex) works on both sides of that pipeline — as a source and as a target. This article walks the end-to-end flow an agent actually takes, with the exact arguments.
Connecting
The MCP endpoint lives at /mcp and authenticates with OAuth; spec-compliant clients discover the authorization server and run the flow automatically, with no pre-registered client id needed. From Claude Code:
claude mcp add --transport http coremodels https://coremodels.example.com/mcp
then run /mcp inside the session to complete OAuth. In Claude Desktop or claude.ai, add a custom connector pointing at the same URL. /mcp serves the read-only (Viewer-role) tools — which includes everything in this article. Write tools such as node CRUD live on the separate /mcp-admin endpoint; you do not need it here, because transform_schema writes nothing.
The tool contract
transform_schema takes these arguments — the input schema declares additionalProperties: false, so names must match exactly:
graphProjectId(required) — 32-char hex, pattern^[a-f0-9]{32}$. It scopes authorization only; the call is stateless.sourceFormat(required) —jsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | odm.sourceSchema(required) — the source text. For ShEx: the ShExC document as a string.targetFormat(required) —jsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | synapse.targetHintFormat/targetHintSchema— the schema to map toward; required for inferred mapping.mappingKind—inferred(default) |explicit|ai.guide— forexplicit: the mapping-guide JSON as text; forai: optional free-text guidance.caseInsensitive— inferred matching ignores label case (defaulttrue).vendor— only forsqloutput;synapseOrg/synapseName/synapseVersion— only forsynapseoutput. ShEx itself needs no format-specific options.
The scenario, step by step
The agent has the ontology team's shapes:
PREFIX schema: <https://schema.org/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
schema:ProductShape {
schema:name xsd:string ;
schema:sku xsd:string ;
schema:price xsd:decimal ? ;
}
and the app team's current JSON Schema, which it uses as the target hint so the inferred strategy has something to match labels against. The tool call it issues:
{
"graphProjectId": "0123456789abcdef0123456789abcdef",
"sourceFormat": "shex",
"sourceSchema": "PREFIX schema: <https://schema.org/>\nPREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n\nschema:ProductShape {\n schema:name xsd:string ;\n schema:sku xsd:string ;\n schema:price xsd:decimal ? ;\n}",
"targetFormat": "jsonschema",
"targetHintFormat": "jsonschema",
"targetHintSchema": "{ \"$id\": \"Product\", \"type\": \"object\", \"title\": \"Product\", \"properties\": { \"name\": { \"type\": \"string\" }, \"sku\": { \"type\": \"string\" }, \"price\": { \"type\": \"number\" } } }",
"mappingKind": "inferred",
"caseInsensitive": true
}
Why this lines up: on decode, the shape name schema:ProductShape becomes a type labeled Product (the Shape suffix is stripped), and each predicate's local name labels an element — name, sku, price. Those match the hint's title and property names one-to-one, and the types are compatible (xsd:decimal decodes to our Double kind; the hint's number does too). The inferred interpreter aligns them, and the plan it produces passes the same validation gate every strategy passes — inference earns no shortcut.
The tool returns one JSON document:
{
"success": true,
"schema": { ... }, // the produced JSON Schema
"plan": { "operations": [ ... ] }, // the executed plan — replayable
"lossiness": [ ... ] // the honest ledger, possibly empty
}
Three things the agent does with it, in order. First, it hands schema to the app team — and inside it, the name property carries "x-maps-to": { "schema": "https://schema.org/name" }, because ShEx predicates are IRIs and the coder lifts them into cross-standard mappings automatically. Second, it reads lossiness aloud: each record is a kind (StructuralDrop, TypeApproximation, ConstraintRelaxation, or SemanticNarrowing), a path, and a plain-English explanation — which is precisely the "tell me what didn't survive" half of the user's request. Third, it saves plan: the same conversion can be replayed deterministically later, and a stored plan is revalidated by the same gate when it is.
The reverse direction: shapes out of a governed project
ShEx is also a first-class output. Two routes, depending on where the schema lives:
If the source is another format in hand — say the app team's JSON Schema should become shapes for the RDF validation pipeline — the agent calls transform_schema again with the directions swapped: sourceFormat: "jsonschema", targetFormat: "shex", and a hint to map toward. Elements carrying mapsTo URIs come back as real prefixed predicates (schema:name); everything else is emitted deterministically under our cm: namespace.
If the source is the governed project itself, there is a dedicated read-only tool, export_shex, on the same /mcp endpoint:
{
"graphProjectId": "0123456789abcdef0123456789abcdef",
"includeCardinality": true
}
Optional arguments narrow it: spaceId scopes to one space, nodeIds selects specific type nodes (omit it to export all types), and includeCardinality (default true) controls whether the ShEx cardinality markers ?, *, + are emitted. The result is the project's schema as a ShExC string — a natural final step after list_projects and get_project_summary when an agent is asked "give me shapes for what we govern."
When the agent reaches for mappingKind: ai
If labels do not line up and nobody has authored an explicit guide, the agent can set "mappingKind": "ai", optionally with free-text guide hints. Know the rules before you let it: the proposal is generated server-side and then validated by the same gate as every other plan, with at most one repair attempt — a rejected repair is a rejection, and the gate is never relaxed. It requires a server-configured Anthropic API key and Editor or Admin membership on the scoping project (Viewer is not enough), and it sends the schema content to the Anthropic API server-side — the tool advertises this openly in its annotations. Without the key, the call is declined honestly with a pointer to explicit or inferred. Any self-reported confidence in the proposed plan is advisory only; the gate never trusts it.
When it goes wrong
Failures come back as tool errors with the reason spelled out, which matters for agents because a plain-English error is a recoverable one. A bad format key returns the full list of valid keys to pick from. A ShExC document that will not parse returns Could not parse the 'shex' schema: with the underlying reason; a document the decoder rejects (an empty one, say) returns Could not decode the source schema: with the path and message attached, and the same pattern covers a broken target hint, a rejected mapping guide, a plan the gate refused, and an encode the target format could not complete. Nothing surfaces as an opaque failure — every stage of the pipeline reports in the same shape, so the agent's retry can actually address the cause.
That is the whole loop: one connector, one stateless tool for any-to-any conversion with ShEx on either end, one dedicated exporter for governed shapes, and a lossiness ledger that lets the agent answer the honest half of the question instead of hand-waving it. For the rest of the MCP tool inventory, see the CoreModels MCP quickstart in our docs.