Ontology Alignment You Can Delegate: `transform_schema` and OWL Over MCP
A partner sends you their vocabulary as a Turtle file. You need your catalog expressed in their terms so the two sides can exchange data, and you need a record of which of your concepts had no counterpart on their side. That used to be an afternoon in an ontology editor plus a spreadsheet nobody trusts.
Ontology Alignment You Can Delegate: transform_schema and OWL Over MCP
A partner sends you their vocabulary as a Turtle file. You need your catalog expressed in their terms so the two sides can exchange data, and you need a record of which of your concepts had no counterpart on their side. That used to be an afternoon in an ontology editor plus a spreadsheet nobody trusts.
With the CoreModels MCP server attached, it is one tool call — and the tool hands back three things: the aligned ontology, the executed mapping plan, and the ledger of what did not carry across. This article walks transform_schema end to end for the owl format key.
Connecting
The CoreModels MCP server speaks stateless streamable HTTP with OAuth. From Claude Code:
claude mcp add --transport http coremodels https://coremodels.example.com/mcp
Then run /mcp inside Claude Code to complete the OAuth flow. In claude.ai or Claude Desktop, add a custom connector pointing at the same /mcp URL. Spec-compliant clients discover the authorization server and run the PKCE authorization-code flow automatically, with dynamic client registration — no pre-registered client id to arrange.
Generic JSON-configured clients want:
{
"mcpServers": {
"coremodels": { "type": "http", "url": "https://coremodels.example.com/mcp" }
}
}
transform_schema is read-only and stateless. It never writes to a project; the project id it takes scopes authorization only.
The tool, precisely
| Argument | Required | Meaning |
|---|---|---|
graphProjectId | yes | 32-char hex project id, pattern ^[a-f0-9]{32}$ — the authorization scope |
sourceFormat | yes | jsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | odm |
sourceSchema | yes | the source schema text; for owl, the Turtle document as a string |
targetFormat | yes | jsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | synapse |
vendor | no | SQL output only: postgres (default) | mysql | sqlserver |
synapseOrg / synapseName / synapseVersion | no | Synapse output only: the registered-schema $id parts |
targetHintFormat / targetHintSchema | no | the schema to map toward; required for inferred mapping |
mappingKind | no | inferred (default) | explicit | ai |
guide | no | explicit: the mapping-guide JSON as text; ai: optional free-text guidance |
caseInsensitive | no | inferred matching ignores label case, default true |
The input schema closes with additionalProperties: false, so a schema-validating client rejects a misspelled argument instead of silently sending it.
Two OWL-specific notes. First, owl has no format-specific options: vendor belongs to SQL output and the synapse* trio to Synapse output, and Turtle needs neither. Second, owl appears in both format lists above — it decodes and it encodes — which is not true of every key on the surface (odm decodes only, synapse encodes only).
One requirement trips up first-time callers. This is a mapping tool, and the default inferred strategy aligns the source toward a hint. Call it with only the four required arguments and it declines, quoting the engine verbatim:
Could not produce a mapping plan: inference: The inference resolver requires a target IR to match against.
For a straight format conversion — same model, different syntax — hand the engine the source as its own hint. Every label matches itself and the plan becomes a 1:1 alignment of the whole schema.
The scenario: our catalog, their vocabulary
Here are the two ontologies. Ours, catalog.ttl:
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ex: <https://example.org/catalog#> .
@prefix pt: <https://partner.example.net/model#> .
ex:Product a owl:Class ;
rdfs:label "Product" ;
rdfs:subClassOf [ a owl:Restriction ; owl:onProperty ex:sku ; owl:minCardinality 1 ] ,
[ a owl:Restriction ; owl:onProperty ex:sku ; owl:maxCardinality 1 ] ,
[ a owl:Restriction ; owl:onProperty ex:listPrice ; owl:maxCardinality 1 ] .
ex:sku a owl:DatatypeProperty ; rdfs:label "sku" ;
rdfs:domain ex:Product ; rdfs:range xsd:string .
ex:listPrice a owl:DatatypeProperty ; rdfs:label "listPrice" ;
rdfs:domain ex:Product ; rdfs:range xsd:decimal .
ex:tag a owl:DatatypeProperty ; rdfs:label "tag" ;
rdfs:domain ex:Product ; rdfs:range xsd:string .
Theirs, partner.ttl — same concepts, different names and different IRIs:
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix pt: <https://partner.example.net/model#> .
pt:Item a owl:Class ; rdfs:label "Product" .
pt:code a owl:DatatypeProperty ; rdfs:label "sku" ;
rdfs:domain pt:Item ; rdfs:range xsd:string .
pt:price a owl:DatatypeProperty ; rdfs:label "listPrice" ;
rdfs:domain pt:Item ; rdfs:range xsd:decimal .
Note that catalog.ttl declares the pt: prefix even though it never uses it. That is deliberate: the encoder emits @prefix lines only for prefixes it can resolve from the source document's preserved prefix map, so declaring the target namespace up front is what makes the produced document self-contained.
The agent calls transform_schema with both files as strings:
{
"graphProjectId": "0123456789abcdef0123456789abcdef",
"sourceFormat": "owl",
"sourceSchema": "@prefix owl: <http://www.w3.org/2002/07/owl#> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n@prefix ex: <https://example.org/catalog#> .\n@prefix pt: <https://partner.example.net/model#> .\n\nex:Product a owl:Class ;\n rdfs:label \"Product\" ;\n rdfs:subClassOf [ a owl:Restriction ; owl:onProperty ex:sku ; owl:minCardinality 1 ] ,\n [ a owl:Restriction ; owl:onProperty ex:sku ; owl:maxCardinality 1 ] ,\n [ a owl:Restriction ; owl:onProperty ex:listPrice ; owl:maxCardinality 1 ] .\n\nex:sku a owl:DatatypeProperty ; rdfs:label \"sku\" ;\n rdfs:domain ex:Product ; rdfs:range xsd:string .\n\nex:listPrice a owl:DatatypeProperty ; rdfs:label \"listPrice\" ;\n rdfs:domain ex:Product ; rdfs:range xsd:decimal .\n\nex:tag a owl:DatatypeProperty ; rdfs:label \"tag\" ;\n rdfs:domain ex:Product ; rdfs:range xsd:string .",
"targetFormat": "owl",
"targetHintFormat": "owl",
"targetHintSchema": "@prefix owl: <http://www.w3.org/2002/07/owl#> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n@prefix pt: <https://partner.example.net/model#> .\n\npt:Item a owl:Class ; rdfs:label \"Product\" .\n\npt:code a owl:DatatypeProperty ; rdfs:label \"sku\" ;\n rdfs:domain pt:Item ; rdfs:range xsd:string .\n\npt:price a owl:DatatypeProperty ; rdfs:label \"listPrice\" ;\n rdfs:domain pt:Item ; rdfs:range xsd:decimal .",
"mappingKind": "inferred"
}
What comes back
The tool returns one JSON payload with four keys — success, schema, plan, lossiness. The schema string is a complete Turtle document:
@prefix ex: <https://example.org/catalog#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix pt: <https://partner.example.net/model#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<https://example.org/catalog#OwlSchema> a owl:Ontology ;
rdfs:label "owl-schema" .
pt:Item a owl:Class ;
rdfs:label "Product" ;
owl:equivalentClass ex:Product ;
rdfs:subClassOf [ a owl:Restriction ; owl:onProperty pt:code ; owl:minCardinality "1"^^xsd:nonNegativeInteger ] ;
rdfs:subClassOf [ a owl:Restriction ; owl:onProperty pt:code ; owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ;
rdfs:subClassOf [ a owl:Restriction ; owl:onProperty pt:price ; owl:maxCardinality "1"^^xsd:nonNegativeInteger ] .
pt:code a owl:DatatypeProperty ;
rdfs:label "sku" ;
owl:equivalentProperty ex:sku ;
rdfs:domain pt:Item ;
rdfs:range xsd:string .
pt:price a owl:DatatypeProperty ;
rdfs:label "listPrice" ;
owl:equivalentProperty ex:listPrice ;
rdfs:domain pt:Item ;
rdfs:range xsd:decimal .
Look at what the alignment did. The model now lives under the partner's IRIs — and it asserts the alignment in the artifact: owl:equivalentClass ex:Product, owl:equivalentProperty ex:sku, owl:equivalentProperty ex:listPrice. Our original identities were lifted into cross-standard mapsTo assertions when the source was decoded, and on the way out they became equivalence axioms. The mapping is not in a side file the exchange partner has to trust; it is in the ontology, in standard OWL, readable by any reasoner. The cardinality restrictions moved with the properties, so "sku is required and single-valued" is still stated after the rename.
(The document header says owl-schema because catalog.ttl has no owl:Ontology subject. Add one with an rdfs:label and your name rides through.)
The plan is the executed alignment, three operations, each stamped with its origin:
{
"operations": [
{ "kind": "TypeMapping", "origin": "Inferred",
"sourceTypeId": "ex:Product", "targetTypeId": "pt:Item", "targetLabel": "Product" },
{ "kind": "ElementMapping", "origin": "Inferred",
"sourceElementIds": ["ex:sku"], "targetElementIds": ["pt:code"] },
{ "kind": "ElementMapping", "origin": "Inferred",
"sourceElementIds": ["ex:listPrice"], "targetElementIds": ["pt:price"] }
]
}
origin: "Inferred" is the agent's cue that these are guesses from label and type matching, not authored intent. Two element mappings for three properties — and the third is exactly what the ledger reports:
[
{ "kind": "StructuralDrop",
"path": "Type[ex:Product].ex:tag",
"explanation": "Element is a member of the mapped type but no operation maps or drops it." }
]
tag has no counterpart in the partner vocabulary, so it did not travel. This is the answer to the spreadsheet question, produced automatically. A good agent surfaces it rather than swallowing it: "Two of your three product properties align. tag has no equivalent on their side — drop it, or ask them to add one."
The three mapping kinds, from an agent's seat
inferred(default) — label and type matching against the hint, case-insensitive unless you setcaseInsensitive: false. Good first move whenever the two models use recognizable names.explicit— you supplyguideas mapping-guide JSON text:autoMatchByMapsTo,fieldMappingsof{sourceElementIds, targetElementIds, transformName},taxonomyDirectives,drops. For OWL this is unusually strong, because every decoded entity already carries its IRI as amapsToassertion — soautoMatchByMapsToaligns by identity rather than by name. Two ontologies that each declare equivalence to the same external IRI match even when their labels differ. Unknown keys in a guide are rejected with a path-carrying error.ai— a server-side proposal. Know the boundaries before offering it: it requires Editor or Admin membership on the scoping project and a server-configured Anthropic API key, and it sends the schema content to the Anthropic API server-side — the tool advertises that openly with an open-world hint. The proposal then passes the same validation gate as every other plan, with at most one repair attempt. A rejected repair is a rejection; the gate is never relaxed for a model. Without a key the tool declines honestly rather than falling back silently.
The companion tool
When the ontology you want is not in a file but in a CoreModels project, use export_owl instead. Arguments are graphProjectId (required) and an optional spaceId; it is read-only. It returns the project's governed schema as Turtle, and when anything could not be expressed, the ledger is prepended as # lossiness: comment lines at the top of the document — comments are valid Turtle, so the honesty report stays attached to the artifact through copy-paste, email, and object storage.
Why the shape suits agents
Arguments are flat strings, so there is no client-side serialization cleverness to get wrong. Failures come back as structured messages an agent can quote — including the decoder's $: Line 1: … syntax errors, which point at the exact line of the user's file. The ledger turns "did we lose anything?" from a judgment call into a list to iterate. And the plan means a one-off conversion can graduate into a reviewed, replayable pipeline without redoing the alignment.
For the full tool inventory and the write-capable admin endpoint, see the MCP quickstart in the CoreModels documentation.