Malloyyo

2026-07-18 · page · source ↗ · source summary

A thin web service that turns Malloy semantic models into MCP endpoints for AI agents, so any LLM can run consistent, governed analytical queries against your data. The AI composes queries against a model (measures, dimensions, joins defined once, correctly) instead of writing SQL from scratch — so the same question always yields the same numbers, with no wrong joins or invented columns.


Architecture

Malloyyo is a Next.js 16 monorepo (~9,500 lines of TypeScript) with three layers:

MCP Engine (packages/mcp-engine/)

A vendor-neutral, zero-dependency library that is the intellectual core. Three sub-layers:

Host layer (src/lib/mcp-host.ts)

Wires the engine to the real world: DB-backed model resolution, connection pooling, instance tagging ([Malloyyo] prefix on tool descriptions), per-call history recording, share-link decoration, and the open_share_link tool. Injects model param on every tool schema for model attribution.

Server runtime (src/lib/malloy.ts)

A sophisticated connection management system for Vercel serverless:

Data model (src/db/schema.ts)

Neon Postgres via Drizzle ORM. Key tables: datasets (status lifecycle, GitHub config), malloy_models (versioned, git provenance, compiled_model_def bytea), malloy_model_files (multi-file models), malloy_artifacts (dashboard artifacts), history (every tool call, time-window sessionized), saved_queries (durable, favoritable), plus full OAuth 2.1 provider tables.

CLI (packages/cli/)

Standalone malloyyo CLI: login, publish (bundle + push model files), status, lint (validate dashboards), mcp (stdio server for Claude), init (scaffold .mcp.json), author/test (launch Claude in develop/explore mode), dashboard dev (local preview).

Key Techniques

Restricted query governance

The explore surface routes ALL queries through loadRestrictedQuery() — the model author controls exactly what an AI can query. No import, no raw SQL, no ##! flags. This is enforced structurally, not by prompt.

SQL withholding on execute:true

The generated SQL is parked on the HOST_ONLY channel — the agent never sees SQL on an executed run. SQL only rides execute:false (the confirmatory-inspect channel). The host records it for auditing.

Source-centric resolution with model_ref backstop

The explore surface is source-centric (matching how users think: "what can I query?"), but addressing stays model-keyed underneath. A bare source resolves against the catalog when unique; ambiguous → "pick one, here are the model_refs."

Field-not-found recovery with sibling detection

queryFieldFix() detects when a "not defined" name is actually defined IN the query body — a sibling-field reference that Malloy doesn't allow. Instead of misleading the agent into hunting for a missing field, it points at the extend: composition fix.

Annotation dual-channel promotion

#" → description (human), #(agent) → instructions (agent). Promoted to first-class fields, then stripped from annotations so text isn't shipped twice.

Problems-as-data, not protocol errors

Every helper/tool failure returns as problems[] with stable codes, positions, and help_topic pointers. isError is reserved for the host (auth, unknown tool).

Client-profile-aware result formatting

The host detects the calling client and adapts: clients that hide structuredContent get compact markdown tables — rows can't be hidden behind a citation.

Design Decisions

Comparison

Unlike DAB which auto-generates endpoints from any database schema, Malloyyo requires a semantic model — which IS the governance boundary. Unlike Nubase which provides a full backend platform, Malloyyo is deliberately thin: it serves models, nothing else. Unlike raw MCP servers with fixed tools, Malloyyo's tools are model-driven — the surface changes based on what's published.

The restricted query governance is genuinely novel: the model defines the semantic surface, the engine enforces it structurally, and the AI composes against the model rather than writing SQL — no invented columns, no wrong joins, no fan-out double-counts. The connection pooling with two-level ModelDef caching is unusually sophisticated for a Next.js app, treating serverless cold starts as a first-class problem.


Sources: raw/malloyyo Last updated: 2026-07-18