Skip to main content
Glama

metric-contracts

One governed metric contract, many consumers.

A metric contract declares what a semantic layer usually leaves implicit: formula, components, source, refresh cadence, as-of semantics, allowed dimensions, additivity (additive, semi-additive with the non-additive dimension named, or non-additive), caveats, owner. Four consumers enforce it:

  1. Validator CLI for CI. A semi-additive metric must name its non-additive dimension or the build fails. Allowed-dimension sets must be subsets of the declared registry. Ratios must carry components. Every metric needs an owner, a refresh cadence and as-of semantics.

  2. MCP server. list_metrics, get_contract, plan_query(metric, grain, dimensions) returns allowed or denied with the reason, so an agent reads the contract before it writes SQL.

  3. Chart meaning gate. Given a proposed chart, return veto or allow with the rule that fired: ratios are not stacked, pie'd or summed; semi-additive at a coarse grain takes LAST; pie category cap; funnels only for ordered stages; no duplicate axis values. The model proposes, the rules veto.

  4. Decomposition check. A driver/drag breakdown must sum back to the total change, partition one dimension, and state its method for ratios. Otherwise the "insight" is rejected.

Vocabulary follows Apache Ossie discussion #465 (additivity, non_additive_dimensions[{field, window}]), and the contract ships both as a standalone YAML file and as an Ossie custom_extensions payload. Design notes and the survey of what dbt, Cube, Malloy, LookML, Rill, Snowflake, Databricks and others already support are in docs/design.md.

Install in two minutes

pip install "metric-contracts[all]"          # CLI + MCP server + DuckDB demo
git clone https://github.com/val-ye/metric-contracts && cd metric-contracts

metric-contracts validate examples/saas       # 0 errors
metric-contracts plan active_subscribers --grain month --dims plan -c examples/saas
metric-contracts plan active_subscribers --grain month --dims plan --agg sum -c examples/saas   # denied, P04
python demo/before_after.py                   # the before/after agent demo, offline

Python 3.11 or newer. The core package depends on pydantic, PyYAML and jsonschema only. The mcp, demo and live extras add the MCP SDK, DuckDB and the Anthropic SDK.

Related MCP server: governed-analytics-agent

A contract

contract_version: "0.1"
domain: subscriptions
owner: { team: analytics-engineering, contact: analytics-eng@example.com }
dimensions:
  - { name: date, type: time, grains: [day, week, month] }
  - { name: plan, type: categorical, values: [free, starter, pro, enterprise] }
metrics:
  - name: active_subscribers
    kind: measure
    formula: count(subscription_id)
    source: { table: fct_subscriber_snapshot_daily, grain: [date, subscription_id], time_dimension: date }
    additivity: semi_additive
    non_additive_dimensions:
      - { field: date, window: last }
    dimensions: { allowed: [date, plan, region], time_grains: [day, week, month] }
    refresh: { cadence: daily, lands_at: "06:00Z", settling_window: P1D }
    as_of: { semantics: snapshot, snapshot_at: end_of_day }
    caveats:
      - A month-end figure is the last daily snapshot, never a sum of days.

The full example with an additive measure, a semi-additive snapshot, a non-additive ratio and a distinct count is in examples/saas. The JSON Schema is committed under schema/ and regenerated from the pydantic models in CI.

Before and after

python demo/before_after.py seeds a synthetic DuckDB database and asks two scripted agents the same question: total active subscribers by month for Q1, split by plan. Measured output of the committed seed:

  • Agent A has only the table name. It writes COUNT(subscription_id) ... GROUP BY month over a daily snapshot table and reports a total 23.3x the month-end truth.

  • Agent B calls plan_query first, receives {"date": "last", "plan": "sum"} and a reference query, and matches the month-end truth row for row.

  • Agent B then asks to SUM across months and is denied with P04_sum_over_non_additive_dim: active_subscribers is semi-additive over date.

python demo/before_after.py --live runs the same two prompts through a real model with plan_query exposed as a tool (needs ANTHROPIC_API_KEY and the live extra) and prints the SQL each one wrote.

MCP server

metric-contracts serve -c examples/saas            # stdio
metric-contracts serve -c examples/saas --http     # streamable-http on 127.0.0.1:8765

Claude Desktop / Claude Code config:

{ "mcpServers": { "metric-contracts": { "command": "metric-contracts", "args": ["serve", "-c", "/path/to/contracts"] } } }

Tools: list_metrics, get_contract, plan_query, check_chart, check_decomposition, list_dimensions, validate_contracts. Resources: contract://schema, contract://schema/payload, contract://{domain}. The server's instructions tell the agent to plan before it queries and to treat a denial as an answer, not an obstacle.

CI

- uses: val-ye/metric-contracts@v0
  with:
    paths: contracts/

Or pipx run metric-contracts validate contracts/ --format github. Exit code 1 on any error; findings are emitted as GitHub annotations.

Rules

Every rule names the contract field it reads, so a veto is explainable and testable.

Family

Ids

What they check

Validate

V00-V15

schema, semi-additive names its dimension, allowed dims in registry, grains, ratio components, distinct count non-additive, source or components, owner, refresh, as-of, dangling components, ordinal funnel dims, unique names, no TODO markers

Plan

P01-P06

unknown metric, dimension not allowed, grain not allowed, sum over the non-additive dimension, ratio aggregated directly, distinct count rolled up

Chart

C01-C14

ratio stacked / pie'd / summed, semi-additive at coarse grain takes the declared window, semi-additive stacked over time, distinct count summed, pie cap, pie needs additive, funnel needs ordinal stages, duplicate axis values, dimension and grain allowed, settling window unmarked, series density

Decompose

D01-D06

sums to total within tolerance, one partition, partition allowed, ratio needs a method, semi-additive deltas are last-to-last, residual disclosed and bounded

Full tables with the field each rule reads: docs/design.md.

Interop

metric-contracts import dbt target/semantic_manifest.json --out contracts/   # stubs with TODO markers
metric-contracts import ossie model.yaml --out contracts/
metric-contracts export ossie -c contracts/ --out model.ossie.yaml           # payload in custom_extensions

The dbt importer maps non_additive_dimension.window_choice max to last and min to first, count_distinct to a non-additive distinct count, ratio metrics to components. Refresh, as-of, owner and allowed dimensions are never inferred; they come back as TODO and validate fails until a human declares them.

What this is not

metric-contracts is not a query engine, not a semantic layer, and not a replacement for dbt, Cube, MetricFlow, Malloy or LookML. It does not compute metrics. It holds the declarations those tools leave implicit, checks them in CI, and hands them to agents, chart builders and insight generators so the rules are enforced where the mistakes happen. The reference SQL plan emits is a DuckDB illustration of the aggregation plan, not a dialect-aware compiler.

Development

uv venv && uv pip install -e ".[dev]"
pytest
ruff check src tests demo examples && mypy
metric-contracts schema --write schema      # regenerate after a model change

Apache-2.0. Everything in examples/ is synthetic.

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
    Not graded
    quality
    C
    maintenance
    Enables AI agents to inspect schemas, walk Delta table history, run read-only DuckDB queries, and check data-quality and job-run status safely.
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables AI agents and their users to ask business questions in plain language and receive governed metric values, breakdowns, share-of-total figures, and the exact parameterized SQL behind them, drawn from a single semantic model rather than ad-hoc SQL. It also lets clients inspect available metrics and their definitions, and preview optimized queries without executing them.
    4
    MIT