OWL logoMCP

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

ArgumentRequiredMeaning
graphProjectIdyes32-char hex project id, pattern ^[a-f0-9]{32}$ — the authorization scope
sourceFormatyesjsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | odm
sourceSchemayesthe source schema text; for owl, the Turtle document as a string
targetFormatyesjsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | synapse
vendornoSQL output only: postgres (default) | mysql | sqlserver
synapseOrg / synapseName / synapseVersionnoSynapse output only: the registered-schema $id parts
targetHintFormat / targetHintSchemanothe schema to map toward; required for inferred mapping
mappingKindnoinferred (default) | explicit | ai
guidenoexplicit: the mapping-guide JSON as text; ai: optional free-text guidance
caseInsensitivenoinferred 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 set caseInsensitive: false. Good first move whenever the two models use recognizable names.
  • explicit — you supply guide as mapping-guide JSON text: autoMatchByMapsTo, fieldMappings of {sourceElementIds, targetElementIds, transformName}, taxonomyDirectives, drops. For OWL this is unusually strong, because every decoded entity already carries its IRI as a mapsTo assertion — so autoMatchByMapsTo aligns 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.