zigmo
Provides tools for querying business metrics stored in a DuckDB star schema, compiling semantic requests into parameterized SQL, executing guarded queries, and returning results with the generated SQL and DuckDB plans.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@zigmo What share of gross revenue do our top 3 countries have?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
zigmo
Your business data, explained by an ape. zigmo is a semantic-layer engine for business metrics, exposed as an MCP server. Ask in plain words — "which countries drive our revenue?" — and an AI agent answers with governed numbers, the share of the total, and the exact SQL behind them. No dashboard required.
zig is the zigzag every metric draws over time (and a nod to sigma); mo is the monkey. Pronounced zig-mo. Part of the nxape family.
flowchart LR
A[Online Retail II<br/>1.07M public lines] -->|make data| B[(DuckDB<br/>star schema)]
S[semantic/model.yaml<br/>metrics + dimensions] --> C[compiler]
C --> G[guardrails]
G --> B
M[MCP server<br/>4 tools] --> C
U[Claude / any MCP client] <--> MWhy
Dashboards answer the questions someone predicted. Letting an LLM write SQL answers any question, but each answer may define "revenue" differently, cost an unbounded scan, or open an injection hole. zigmo sits in between:
One meaning per metric.
gross_revenue,return_rateand friends are defined once insemantic/model.yaml. Every answer uses the same definition.Agents ask for names, not SQL. The compiler turns metrics, dimensions and filters into one parameterized query, joining only the tables it needs.
Guardrails by default. Read-only connection, allowlists, a cost pre-flight, a timeout and an audit log of every call.
Evals that prove it. 15 business questions checked against hand-written oracle queries in CI, plus an optional eval where Claude answers them through the MCP tools.
Related MCP server: governed-analytics-agent
Quickstart
Requires uv.
git clone https://github.com/unprimathe/zigmo && cd zigmo
make setup # install
make data # download the dataset (45 MB) and build data/zigmo.duckdb (~2 s)
make test # 30 unit and guardrail tests
make evals # 15 business questions vs. oracle queriesConnect it to Claude Code:
claude mcp add zigmo -- uv --directory "$PWD" run zigmo-serveror to Claude Desktop (claude_desktop_config.json):
{ "mcpServers": { "zigmo": { "command": "uv",
"args": ["--directory", "/absolute/path/to/zigmo", "run", "zigmo-server"] } } }Then ask: "What share of gross revenue do our top 3 countries have?"
Tools
Tool | What it does |
| The catalog: every metric and dimension with its business meaning |
| How one metric is defined (formula, filter, format) |
| Metrics split by up to 3 dimensions, with filters, date range, sorting and optional share of total |
| The optimized SQL and DuckDB plan for a request, without running it — paste it into any BI tool |
All four are annotated read-only.
Example
query_metrics(metrics=["gross_revenue"], dimensions=["country"], limit=3, with_share=true)
country | gross_revenue (GBP) | share |
United Kingdom | 16,801,004.62 | 85.5% |
EIRE | 623,414.16 | 3.2% |
Netherlands | 549,773.41 | 2.8% |
The response also carries the SQL that produced it:
SELECT co.country AS country,
SUM(f.amount) FILTER (WHERE NOT f.is_return) AS gross_revenue,
CAST((SUM(f.amount) FILTER (WHERE NOT f.is_return)) AS DOUBLE)
/ NULLIF(SUM(SUM(f.amount) FILTER (WHERE NOT f.is_return)) OVER (), 0) AS gross_revenue_share
FROM fact_sales f
JOIN dim_country co ON co.country_key = f.country_key
GROUP BY ALL
ORDER BY gross_revenue DESC NULLS LAST
LIMIT 3The semantic model
Metrics | Dimensions |
|
|
Star schema: fact_sales (one row per invoice line, 1,021,252 rows) with dim_date,
dim_product, dim_customer and dim_country. How returns, guest checkouts and
non-merchandise lines are handled is in ADR 3.
Why these guardrails
Guardrail | Stops |
Allowlist (names must exist in the model) | Questions answered with made-up definitions; SQL fragments smuggled in as names |
Bound parameters for every filter value | SQL injection: |
Limits (≤8 metrics, ≤3 dimensions, ≤1000 rows) | Runaway result sets in an agent loop |
Read-only connection | Any write, even if every other layer failed |
Pre-flight: | Queries like revenue by customer × product × day (≈20 billion possible groups), rejected with advice to use coarser dimensions |
Timeout | Long scans holding the database |
Audit log (JSONL) | Unanswerable "what did the agent ask?" — every call, its SQL, rows and rejections |
Each one has a test in tests/. Details in ADR 4.
Evals
make evals— deterministic, runs in CI. Each question inevals/questions.yamlhas a semantic request and an independent, hand-written oracle query; the results must match. Breaking a metric definition on purpose turns the run red (7/15), which is how we know the eval protects something.make evals-llm— Claude answers each question using only the MCP tools; a question passes when its last query returns the oracle's rows. Needs Anthropic credentials and costs money, so it is not in CI.
Layout
semantic/model.yaml the semantic model
src/zigmo/ingest.py download + clean + star schema
src/zigmo/semantic_layer.py model loading and validation
src/zigmo/compiler.py request -> parameterized SQL
src/zigmo/engine.py guardrails, execution, audit
src/zigmo/server.py MCP server
tests/ unit and guardrail tests on a hand-checkable fixture
evals/ deterministic and LLM evals
docs/adr/ architecture decisionsData
Online Retail II by Daqing Chen, UCI Machine Learning Repository, doi:10.24432/C5CG6D, licensed CC BY 4.0. The data is downloaded at build time and not redistributed here.
License
Code: MIT. Built by Roland Sencial at nxape.
Available Tools
4 toolsdescribe_metricBRead-onlyIdempotent
Explain how a metric is defined (formula, filter, format).
Inputs: name (required).
explain_queryARead-onlyIdempotent
Return the optimized SQL and DuckDB plan for a request, without running it.
Same arguments as query_metrics. Useful to paste the query into a BI dashboard.
Inputs: metrics (required), limit, date_to, filters, +5 more.
list_metricsARead-onlyIdempotent
List every metric and dimension available, with its business meaning.
Inputs: no named parameters.
query_metricsARead-onlyIdempotent
Compute metrics, optionally split by dimensions.
Args:
metrics: metric names from list_metrics, e.g. ["gross_revenue", "orders"].
dimensions: up to 3 dimension names, e.g. ["country"] or ["month"].
filters: dimension -> allowed values, e.g. {"country": ["France", "Germany"]}.
date_from / date_to: inclusive ISO dates (YYYY-MM-DD) on the invoice date.
order_by: a requested metric or dimension. Defaults to the first metric.
descending: sort direction.
limit: max rows (1-1000).
with_share: add <metric>_share = share of the total across the returned groups.
Inputs: metrics (required), limit, date_to, filters, +5 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 4 tools
The four tools target distinct stages: discovery (list_metrics), definition (describe_metric), execution (query_metrics), and dry-run inspection (explain_query). list_metrics vs describe_metric and query_metrics vs explain_query share the same domain and arguments, so there is mild potential for confusion, but the descriptions clearly separate 'list all', 'explain one', 'run', and 'don't run'.
All four names follow a clean verb_noun snake_case pattern (list_metrics, describe_metric, query_metrics, explain_query). The only deviation is describe_metric using the singular 'metric' while others are plural, which is a natural, readable choice rather than an inconsistency.
Four tools is a tight, well-scoped surface for a metrics/semantic-layer server: discover, describe, query, and preview. It is slightly lean, with no separate tool for browsing dimensions or filters, but every tool earns its place and nothing is redundant.
The read lifecycle is fully covered—discovery, definition, querying with dimensions/filters/date ranges, and an explain/dry-run path that complements execution. Minor gaps exist (no standalone dimension listing and no validation-only or saved-query operations), but agents can work around these via list_metrics and explain_query.
Maintenance
Related MCP Connectors
Build a semantic layer of reusable metrics and query it in plain words. Share models with your team.
Answer questions using your semantic layer and company knowledge graph.
The grounded data layer for any LLM: governed SQL, metrics, lineage and catalog over your data.
Ask your organization's connected business data in plain language, with each person's permissions.
Related MCP Servers
- AlicenseBqualityBmaintenanceEnables agents to interact with a governed semantic layer for querying and authoring metrics, providing tools for discovery, planning, validation, and execution of analytics queries.3080 PyPI3Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables agents to answer analytics questions with provable correctness by resolving metrics through the semantic layer, tracing lineage, traversing knowledge graphs, checking freshness, and searching glossary definitions, while enforcing access controls and logging every call.MIT
- AlicenseAqualityCmaintenanceEnables AI agents to list metrics and dimensions, inspect definitions, and run governed metric queries against Apache Iceberg tables through a semantic layer, without writing SQL or accessing raw rows or columns outside their role.5Apache 2.0
- AlicenseBqualityBmaintenanceEnables AI agents to safely describe, query, and compute metrics across SQL databases using open-format semantic models, custom context, and skills, with read-only and dialect-checked execution.165 PyPIApache 2.0