Agents and MCP
Ask a language model to convert one system’s records into another’s and it will
do it. It will match dob to birthDate because those look alike, decide that
status: "VOID" is probably cancelled, and produce output that is right often
enough to be dangerous. What it cannot do is tell you which of its guesses were
guesses.
An ontology replaces the guessing with something the publisher actually said. The correspondences are stated, the code translations are enumerated, and the places where a conversion loses information are marked as lossy. That is exactly the material a model is otherwise inventing.
What an ontology gives an agent
Section titled “What an ontology gives an agent”Meaning, not just shape. A JSON Schema says status is a string. The
model says what a Person is in this system and the
vocabulary enumerates every status code with its display
text and definition. A model reading the schema alone has to infer the domain;
reading the ontology, it is told.
Correspondences the publisher stands behind. Each
mapsTo entry names an external schema and how
closely the alignment holds. An agent does not have to decide whether this
system’s Person is FHIR’s Patient. Somebody who knows has already answered.
An honest signal about loss. type is one-to-one, one-to-many,
many-to-one or partial. A partial alignment is the agent’s cue to surface
the conversion for review rather than to complete it quietly, and it is a cue
that arrives before the data does.
Executable transformations, where they exist. When mapping is present it
points at a FHIR StructureMap or ConceptMap. Running it beats asking a model to
reproduce it. The published map is deterministic, reviewable and the same every
time, which are three things a generated transformation is not.
Prefer the mapping to the model’s judgement
Section titled “Prefer the mapping to the model’s judgement”The useful discipline is a fallback order, not a choice:
- A published
mapping. Execute it. The model’s job is to arrange the call, not to perform the conversion. - A
mapsTowith no mapping. The alignment is stated, so the target is settled; the agent works out the field correspondences between two known schemas rather than between two unknown systems. Much smaller problem, and one a reviewer can check. - No alignment at all. This is the case an ontology cannot help with, and
the agent should say so rather than proceed. A model with no
mapsTois invisible to integration, which is whyvalidateBundlewarns about it.
Whatever produced the output, validate it against the target schema before returning it. Vocabularies are published as JSON Schema, so an invented code fails an ordinary validator, without anyone having to trust the model’s recollection of the list.
Serving an ontology over MCP
Section titled “Serving an ontology over MCP”An agent should not be handed a whole ontology. A large one does not fit a context window, most of it is irrelevant to the task, and paying attention to forty models to convert one record makes the conversion worse rather than better.
The format already anticipates this. Discovery starts at one well-known URI and fetches outward, and a consumer takes only what the task needs. That is the same access pattern a tool interface wants, which makes MCP a natural fit: the server fetches, the agent asks.
A tool surface that follows the format’s own shape:
| Tool | |
|---|---|
find_ontology |
Takes a domain, returns the ontology from its well-known URI. |
list_models |
Names, ids and categories only. Enough to choose, not enough to fill a context window. |
get_model |
One model, with its relationships and alignments. |
get_schema |
The JSON Schema a model points at. |
get_vocabulary |
One code list, or the schema derived from it. |
find_alignment |
Given a model and a target system, the mapsTo entry and its mapping, if there is one. |
find_alignment is the one worth having. It is the question an agent actually
arrives with, and answering it directly keeps the agent from reading every
model to work out which one is relevant.
@openhi/ours is the part of that server you do not have to
write. It parses the documents, assembles them, and hands back typed resources:
import { assembleBundle, wellKnownOntologyUrl } from "@openhi/ours";
const fetchJson = (url: string) => fetch(url).then((r) => r.json());
const ontology = await fetchJson(wellKnownOntologyUrl("example.org"));const bundle = assembleBundle({ ontology, models: [await fetchJson(ontology.models)], vocabularies: [await fetchJson(ontology.vocabularies)],});
// The answer to "what does this become in FHIR?"const alignmentTo = (modelUrl: string, system: string) => bundle.models.get(modelUrl)?.mapsTo?.find((m) => m.system === system);Nothing in this repository ships an MCP server today. The tool surface above is a sketch of one, not a specification, and OURS does not define how an ontology is exposed to an agent. If you build one, the format is the contract; the tool names are yours.
Two things to be careful about
Section titled “Two things to be careful about”An ontology is a claim, not a proof. It says what a publisher believes
about their own data. A one-to-one alignment that quietly drops a field is a
bug in the ontology, and an agent trusting it will produce confidently wrong
output. Validating against the target schema catches the structural half of
this. Nothing catches the semantic half except somebody looking.
A fetched ontology is somebody else’s content. Descriptions, code display text and comments are prose from a third party, arriving inside an agent’s context. Treat them as data to reason about rather than as instructions to follow, the same way you would treat any other fetched document.