Skip to main content
Glama

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.

ci

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] <--> M

Why

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_rate and friends are defined once in semantic/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 queries

Connect it to Claude Code:

claude mcp add zigmo -- uv --directory "$PWD" run zigmo-server

or 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

list_metrics

The catalog: every metric and dimension with its business meaning

describe_metric

How one metric is defined (formula, filter, format)

query_metrics

Metrics split by up to 3 dimensions, with filters, date range, sorting and optional share of total

explain_query

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 3

The semantic model

Metrics

Dimensions

gross_revenue, returns_value, net_revenue, orders, units, customers, aov (derived), return_rate (derived)

country, product, stock_code, customer_id, year, quarter, month, week, day, day_of_week

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: country = "x' OR 1=1 --" matches nothing

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: EXPLAIN + group budget

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 in evals/questions.yaml has 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 decisions

Data

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 tools
describe_metricB
Read-onlyIdempotent

Explain how a metric is defined (formula, filter, format).

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes

TDQS

B3.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and destructiveHint, so the safety profile is covered. The description adds what the explanation contains (formula, filter, format), which is useful context, but says nothing about auth requirements, error behavior for an unknown metric name, or output shape.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, front-loaded sentence with zero filler; the verb and the returned content come first. Nothing is wasted and nothing needs reordering.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only lookup with annotations covering safety and no output schema to explain, the description is nearly sufficient. The remaining gap is the meaning and expected format of the single required argument, which neither the schema nor the description supplies.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

One parameter with 0% schema description coverage, so the description carries the full burden and does not meet it. It never explains what 'name' means — display name vs. metric identifier vs. qualified path — which is exactly the ambiguity that causes failed calls here.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (explain) plus the resource (metric) and enumerates what the explanation covers (formula, filter, format). It is distinguishable from list_metrics and query_metrics, but never names those siblings, so differentiation is left to inference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No when-to-use or when-not-to-use guidance. Nothing tells the agent to pick this over query_metrics when it wants a definition rather than data, or over list_metrics when it wants a catalog. Usage is only loosely implied by the word 'explain'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

explain_queryA
Read-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.
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
date_toNo
filtersNo
metricsYes
order_byNo
date_fromNo
descendingNo
dimensionsNo
with_shareNo

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so safety is covered structurally. The description's 'without running it' is genuinely additive behavioral information, confirming no query execution or side effects occur, which is the main thing an agent needs to know here. It stops short of describing the shape/size of the returned plan.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, with the core non-execution behavior front-loaded. The second sentence ('useful to paste into a BI dashboard') is slightly tangential but does earn its place as a usage hint rather than pure padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 9-parameter tool with no output schema and no schema descriptions, the definition does not explain what any argument means or what the returned plan looks like. The sibling-argument reference is the only mitigation, leaving the agent under-equipped to invoke this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% across 9 parameters, so the schema supplies no parameter meaning at all. 'Same arguments as query_metrics' is a pointer rather than an explanation and conveys nothing about metrics, filters, dimensions, date ranges, or the limit default, leaving the agent to infer everything.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb (return) plus the exact artifacts produced (optimized SQL and DuckDB plan) and adds the crucial qualifier 'without running it'. That qualifier cleanly distinguishes it from the sibling query_metrics, which actually executes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Same arguments as query_metrics' tells the agent the call shape matches the executing sibling, implying it is the non-executing counterpart. The BI-dashboard example gives a concrete use case, but there is no explicit when-not-to-use statement or exclusion relative to query_metrics.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_metricsA
Read-onlyIdempotent

List every metric and dimension available, with its business meaning.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is clear without the description. The description only adds the scope hint that the listing is exhaustive and carries business meaning; it says nothing about pagination, ordering, or result size.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single short sentence with the verb and scope front-loaded and no filler. Nothing could be removed without losing information, and it is appropriately sized for a no-argument listing tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description does the useful work of stating that the result pairs each metric/dimension with its business meaning. Minor gaps remain around return shape (names only vs. full metadata) and whether the listing is paginable, but the essential contract is conveyed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is no parameter semantics to explain; baseline 4 applies. The description correctly does not invent or allude to inputs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb ("List") plus resource ("every metric and dimension") with an added scope note about business meaning. It is distinguishable from query_metrics on the surface, but it never explicitly contrasts itself with describe_metric, which also deals in a single metric's meaning.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description says what the tool returns but nothing about when to call it versus siblings like describe_metric or query_metrics. There is no stated precondition (e.g., "use this first to discover available metrics before querying") and no exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_metricsA
Read-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.
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
date_toNo
filtersNo
metricsYes
order_byNo
date_fromNo
descendingNo
dimensionsNo
with_shareNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint/idempotentHint/non-destructive, so the safety profile is covered. Beyond that, the description adds real behavioral detail: limit is bounded 1-1000, order_by defaults to the first metric, descending defaults to true, dates are inclusive and evaluated on invoice date, and with_share changes the output by appending a share column.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One-line summary is front-loaded, followed by a dense Args block where every line adds a distinct constraint or default. No filler or restated boilerplate.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 9 parameters and no output schema, the description is thorough on inputs but never outlines the shape of the returned rows (beyond the with_share column) or error behavior for invalid metric/dimension names. Minor gap rather than a blocking one.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry everything, and it does: every one of the 9 parameters gets meaning, format, and often an example (filters as dimension->values map, dimensions capped at 3, ISO dates, limit range, with_share semantics). This is far richer than the bare schema titles.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Compute metrics') plus the key scope modifier ('optionally split by dimensions'). It does not distinguish itself from siblings like explain_query, though it does point to list_metrics as the source of valid metric names, which helps with disambiguation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied (compute aggregated metrics, dimensions are optional) and it routes the agent to list_metrics for valid metric names. However, it never states when NOT to use this tool or when explain_query/describe_metric is the better choice, leaving sibling selection to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observeddescribe_metric
    • First observedexplain_query
    • First observedlist_metrics
    • First observedquery_metrics

TDQS

A3.7/5.0

Scored across 4 tools

Disambiguation4/5

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'.

Naming Consistency5/5

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.

Tool Count4/5

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.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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
  • A
    license
    A
    quality
    B
    maintenance
    Enables agents to query governed metrics through MCP, including describing metrics, running deterministic SQL against a warehouse, explaining join path preferences, and searching semantically ambiguous terms.
    8
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Exposes a governed semantic layer to AI agents so they select metrics and dimensions from a closed, engineer-declared vocabulary instead of writing SQL. Validates every typed call against the contract, rejects out-of-contract requests with structured errors and suggestions, and returns query rows together with the compiled SQL and dbt lineage for auditing.
    4
    MIT