MCP reference

ontologiq serve exposes the compiled ontology over the Model Context Protocol on stdio. The tools are generated from your objects — you never write tool code.

Connecting Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "ontologiq": {
      "command": "ontologiq",
      "args": ["serve", "--role", "support", "--project-dir", "/path/to/shop"]
    }
  }
}

Drop --role and the agent gets read tools only. That is the right default for a first connection.

The generated tools

For an object called customer with a relation orders and an object order with an action cancel:

ToolWhat it does
get_customerOne record by identity
list_customerMany records, filterable by computed state
traverse_customer_ordersFrom one customer to their orders
propose_order_cancelPropose the action; never executes

Names are namespaced by object because MCP tool names are global: two objects may both have a cancel action, and validate rejects a project where generated names would collide.

Read tools

get_<object>

Input: the identity columns. Output:

{
  "rows": [{"customer_id": 1, "full_name": "Ada Lovelace", "state": "active"}],
  "count": 1,
  "redacted_properties": ["email"],
  "note": "Properties marked sensitive in the ontology are not returned. They were never read from the database."
}

If more than one row matches the identity, the call fails rather than returning an arbitrary one.

list_<object>

Input: optional state (constrained to the object’s declared states) and limit (default 100, maximum 1000, always clamped server-side regardless of what the client sends).

Results are ordered by identity, so paging by limit is deterministic, and truncation is reported explicitly:

{"rows": [...], "count": 100, "truncated": true}

An agent told nothing about truncation will confidently assert that there are exactly 100 churned customers.

traverse_<object>_<relation>

Input: the identity of the source record, plus an optional limit (same default and ceiling as list_). Returns the related object’s rows. Only the target’s columns come back — the source record’s properties are never even read.

The propose tool

propose_<object>_<action> takes the object’s identity, the action’s declared parameters, and an optional dry_run.

It cannot execute anything. Its outcomes:

outcomeMeaning
dry_runWhat would happen, evaluated against live data. Nothing changed.
pending_approvalA proposal was stored. A human must approve it elsewhere.
executedThe effect was applied. Only reachable for actions declaring no approval, and only when the server ran with --allow-auto-effects.
unknownSame path, but the request was sent and no response came back. The effect may or may not have been applied — do not retry.
effect_failedSame path; the endpoint refused (4xx, a redirect, or 5xx after retries). Nothing was applied.

And its refusals, each an error the agent can act on:

errorMeaning
denied_roleThe asserted role is not in policy.roles. The warehouse was not touched.
precondition_failedrequires does not hold; the message names the actual state.
not_foundNo record with that identity.
ambiguous_identityMore than one row shares that identity.
auto_effects_disabledThe action declares no approval and the server did not opt in.
approval_unevaluableapproval.when references a parameter the call did not supply.

Configuration and transport problems surface separately as invalid_arguments, effect_misconfigured, unknown_object, unknown_tool and internal_error. The table above is the governance refusals — the ones an agent should reason about rather than retry.

The tool description handed to the model states the precondition, the allowed roles and the approval requirement, so the agent knows the rules before calling rather than discovering them by rejection.

What the model never gets

Untrusted data

Values read from your warehouse flow into the model’s context, and a customer name can contain instructions. Before anything is returned, Ontologiq strips control characters, ANSI escapes, Unicode tag characters and bidi overrides — the last two hide text from the human reading the same output — and caps field length.

That reduces the surface; it does not eliminate prompt injection, and nothing at this layer can. The real mitigation is structural: an injected instruction still cannot complete a governed action, because approval lives in a process the model cannot address.

Protocol notes