MACH ODM logoMCP

Converting MACH ODM Entities with an Agent: transform_schema over MCP

Picture the request as it actually arrives: someone pastes a MACH Alliance Open Data Model entity document into a chat and asks their agent, "stand up a Postgres table for this." The document is Markdown — an H1, some prose, YAML blocks inside a schema section. Between that paste and a `CREATE TABLE` statement sits exactly one tool call.

Converting MACH ODM Entities with an Agent: transform_schema over MCP

Picture the request as it actually arrives: someone pastes a MACH Alliance Open Data Model entity document into a chat and asks their agent, "stand up a Postgres table for this." The document is Markdown — an H1, some prose, YAML blocks inside a schema section. Between that paste and a CREATE TABLE statement sits exactly one tool call.

That tool is transform_schema, served on the CoreModels MCP endpoint. It is the whole mapping engine — decode, plan, validation gate, deterministic execution, encode — packaged as a single stateless call an agent can make, with the odm format key accepted as a source. This article walks the exact arguments, a complete agent-driven conversion, and the rules an agent needs to know so it never guesses.

Connecting

The MCP endpoint is https://coremodels.example.com/mcp, a streamable HTTP server secured with OAuth 2.0 (spec-compliant clients discover the authorization server and run the PKCE flow automatically — no pre-registered client id needed). In Claude Desktop or claude.ai it is a custom connector pointed at that URL; in Claude Code it is one command:

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

then /mcp inside the session to complete the OAuth handshake. transform_schema is a read-only tool (it writes nothing to any project), so it is available on the standard endpoint to any authenticated user with Viewer access to the scoping project.

The tool, precisely

transform_schema takes these arguments — and rejects any it does not declare (additionalProperties: false), which is a feature when the caller is a model:

ArgumentRequiredWhat it is
graphProjectIdyes32-char hex project id (pattern ^[a-f0-9]{32}$); scopes authorization only
sourceFormatyesjsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | odm
sourceSchemayesThe source schema text — for odm, the entity document Markdown itself
targetFormatyesjsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | synapse
vendornoFor sql output: postgres (default) | mysql | sqlserver
targetHintFormat, targetHintSchemanoA schema to map toward, for inferred mapping
mappingKindnoinferred (default) | explicit | ai
guidenoExplicit mapping: the SIA mapping-guide JSON. AI mapping: optional free-text guidance
caseInsensitivenoInferred mapping: ignore label case (default true)
synapseOrg, synapseName, synapseVersionnoSynapse output only — not relevant to an ODM source

Note where odm appears: in the source list, not the target list. ODM entities are authored documentation, not a generated artifact, so the format decodes only. An agent that tries "targetFormat": "odm" gets a clean Unknown schema format 'odm' error naming the valid encode formats — a correctable answer, not a mystery.

The scenario, end to end

The user pastes this Store entity document and asks for a Postgres table:

# MACH Alliance Open Data Model: `Store`

## Entity purpose

A physical or digital retail location through which inventory is offered.

## YAML Schema Definition

```yaml
Store:
  type: object
  required:
    - id
  properties:
    id:
      type: string
    name:
      type: string
    capacity:
      type: integer
    status:
      type: string
      enum: [open, closed, seasonal]
```

The agent makes one tool call, passing the Markdown verbatim as sourceSchema:

{
  "name": "transform_schema",
  "arguments": {
    "graphProjectId": "3f2a9c1e5b7d4680a1c2e3f405b6d7c8",
    "sourceFormat": "odm",
    "sourceSchema": "# MACH Alliance Open Data Model: `Store`\n\n## Entity purpose\n\nA physical or digital retail location through which inventory is offered.\n\n## YAML Schema Definition\n\n```yaml\nStore:\n  type: object\n  required:\n    - id\n  properties:\n    id:\n      type: string\n    name:\n      type: string\n    capacity:\n      type: integer\n    status:\n      type: string\n      enum: [open, closed, seasonal]\n```\n",
    "targetFormat": "sql",
    "vendor": "postgres"
  }
}

Under the hood the server converts the Markdown to JSON Schema 2020-12, decodes that into the intermediate representation (with odm provenance stamped on the schema), produces and gates a plan, executes it deterministically, and encodes Postgres DDL. The tool returns one JSON document with three things the agent should read in order:

{
  "success": true,
  "schema": "CREATE TABLE \"Store\" (\n  \"id\" VARCHAR(255) NOT NULL,\n  \"name\" VARCHAR(255),\n  \"capacity\" INTEGER,\n  \"status\" VARCHAR(255)\n);",
  "plan": { "operations": [ "..." ] },
  "lossiness": [
    {
      "kind": "ConstraintRelaxation",
      "path": "Element[Store::status]",
      "explanation": "Postgres has no inline enum; emitted as VARCHAR (the allowed-value constraint is not enforced)."
    }
  ]
}

The schema is the deliverable: id came out NOT NULL because ODM's required list survived the whole pipeline, capacity mapped to INTEGER, strings to VARCHAR(255).

The lossiness ledger is the part a good agent narrates rather than swallows. The record above is the one that matters here: Postgres has no inline enum type, so the status allowed-values constraint (open, closed, seasonal) was relaxed to a plain VARCHAR — and the engine says so, at the exact element path. The right agent behavior writes itself: deliver the DDL and report "the status value list is not enforced at the database layer; enforce it in the application or with a check constraint." That is the difference between a conversion tool and an honest one.

The plan is the executed mapping as a replayable artifact. An agent in a longer workflow can hand it back to the platform later to reproduce the exact same transformation from the exact same source — same plan plus same source yields same output.

What an agent must know about mapping kinds

mappingKind defaults to inferred. For a straight format conversion like the scenario above, the agent supplies no hint and no guide. When the user's goal is alignment — "map this ODM entity onto our existing catalog schema" — the agent adds targetHintFormat and targetHintSchema, and inference matches labels and types against the hint (caseInsensitive defaults to true).

Two escalations exist, both gated identically:

  • explicit requires a guide — SIA mapping-guide JSON with fieldMappings (sourceElementIds, targetElementIds, transformName), taxonomyDirectives, drops, and autoMatchByMapsTo. Unknown keys in a guide are rejected with a path-carrying error, so a hallucinated key fails loudly instead of executing a plan nobody intended.
  • ai asks a server-side Claude proposer to draft the plan. The same validation gate accepts or rejects it — an AI proposal earns no shortcut — and the call has two extra requirements an agent should surface to its user honestly: it needs a server-configured Anthropic key, it requires Editor or Admin membership on the scoping project (Viewer is not enough, because the call spends the server's paid model budget), and it sends the schema content to the Anthropic API server-side.

Whatever the kind, failures come back as structured error text — "Could not decode the source schema," "The plan was rejected by the validation gate," each with paths and messages — so the agent can repair its input rather than retry blindly. A Markdown paste with no ## YAML Schema Definition section, for example, is refused with exactly that diagnosis: it is not an ODM entity document.

From answer to system

transform_schema writes nothing, which is exactly what makes it safe to hand to an agent on the read-only endpoint. When the conversation moves from "show me" to "make it so," the path is deliberate: convert the ODM document to JSON Schema with targetFormat: "jsonschema", then use the import_jsonschema tool — an Admin-role tool served only on the separate admin MCP endpoint — to write it into a project. Read-anywhere, write-deliberately is the boundary, and it is enforced by project roles, not by hoping the agent behaves.

For connector setup, the full tool inventory, and OAuth details, see the CoreModels MCP quickstart in the product docs.