Problem
The Publisher MCP API exposes models to LLM agents primarily through malloy_modelGetText, which returns raw .malloy source. To plan a query, an agent must mentally compile that source — resolving extend:, walking imports, classifying fields as dimensions vs. measures, inferring types, traversing joins, and reconciling givens and source parameters.
This works for large frontier models with enough context and reasoning capacity, but it has real costs:
- Prompt size. A non-trivial model can run several thousand tokens of source text, much of it irrelevant to any one query.
- Hallucination surface. Agents invent fields that don't exist, put measures in
group_by:, or miss joined-field paths because they didn't trace the graph.
- SLM viability. Smaller models (7B–14B) lack the capacity to compile mentally and fail at a much higher rate than on equivalent SQL tasks — despite Malloy's semantic layer being structurally well-suited to LLM use.
- Givens (experimental) raise the stakes. A model with
given: TENANT :: string used for row-level access control depends on the host supplying values correctly. An agent that doesn't see the given contract can construct a query that fails at runtime, or — in misconfigured deployments — silently bypasses intended tenancy boundaries. This is a safety-relevant gap, not just an ergonomic one.
Proposal
Add tool — malloy_schemaGetResolved — as both an MCP tool, returning a flat, typed view of a model derived from the compiled Model object.
Inputs
{
projectName: string,
packageName: string,
modelPath: string,
sourceName?: string // optional; if omitted, return all sources in the model
}
Output (proposed shape — open to revision)
Looking forward to hearing your thoughts on this.
Problem
The Publisher MCP API exposes models to LLM agents primarily through
malloy_modelGetText, which returns raw.malloysource. To plan a query, an agent must mentally compile that source — resolvingextend:, walking imports, classifying fields as dimensions vs. measures, inferring types, traversing joins, and reconciling givens and source parameters.This works for large frontier models with enough context and reasoning capacity, but it has real costs:
group_by:, or miss joined-field paths because they didn't trace the graph.given: TENANT :: stringused for row-level access control depends on the host supplying values correctly. An agent that doesn't see the given contract can construct a query that fails at runtime, or — in misconfigured deployments — silently bypasses intended tenancy boundaries. This is a safety-relevant gap, not just an ergonomic one.Proposal
Add tool —
malloy_schemaGetResolved— as both an MCP tool, returning a flat, typed view of a model derived from the compiledModelobject.Inputs
Output (proposed shape — open to revision)
{ "modelPath": "ecommerce/orders.malloy", "givens": [ { "name": "TENANT", "type": {"kind": "string_type"}, "default_value": {"kind": "stringLiteral", "number_value": "TENANT_A"}, "default_text": "\"TENANT_A\"", "finalized": true, "annotations": [{"value": "# label=\"Tenant ID\"\n"}], }, { "name": "MAX_ROWS", "type": {"kind": "number_type"}, "default_value": {"kind": "number_literal", "number_value": 1000}, "default_text": "1000", "finalized": false, "annotations": [] } ], "sources": [ { "kind": "source", "name": "orders", "annotations": [], "parameters": [ { "name": "min_date", "type": {"kind": "date_type"} } ], "schema": { "fields": [ { "kind": "dimension", "name": "order_id", "type": {"kind": "string_type"} }, { "kind": "measure", "name": "order_total", "type": {"kind": "number_type", "subtype": "decimal"} }, { "kind": "join", "name": "users", "relationship": "one", "schema": { "fields": [ { "kind": "dimension", "name": "country", "type": {"kind": "string_type"} } ] } }, { "kind": "view", "name": "recent", "annotations": [{"value": "# bar_chart\n"}], } ] } } ], "queries": [] }Looking forward to hearing your thoughts on this.