Skip to main content
Glama
25andresbernal

semantic-model-kit

semantic-model-kit

CI

A vendor-neutral semantic model that makes a warehouse legible to LLMs, with compilers to Snowflake semantic views and Open Semantic Interchange, an MCP server that lets agents query governed metrics, and an eval that measures the accuracy gain.

Why this exists

A raw warehouse schema tells an LLM what columns exist. It does not tell it which of two join paths is correct, which of two revenue columns finance means, or that a legacy status code needs decoding before anyone reads it in natural language. Snowflake's engineering team names four production failure modes that follow from asking a model to guess at all of that: metric drift, join ambiguity ("a text-to-SQL system picking the wrong path silently returns a confidently wrong number"), concept mismatch, and tool duplication between BI and agents. (Why you need a semantic model, Snowflake engineering blog.)

Two independent benchmarks measure the effect of resolving that ambiguity instead of asserting it should be resolved. dbt Labs ran the ACME Insurance dataset (15 tables, 11 questions, 20 runs each) through direct text-to-SQL and through a semantic layer: Claude Sonnet 4.6 went from 90.0% to 98.2%, GPT-5.3 Codex from 84.1% to 100%, and the semantic-layer failures that remained were explicit refusals, not confidently wrong numbers. (Semantic layer vs. text-to-SQL, dbt Labs, 2026-04-07.) A second, independent paper found the same shape of result on a related ACME Insurance question set (38 questions): semantic path compilation answered 97.4% correctly across all runs, against 55.3% for a direct text-to-SQL baseline. (Bounded Semantic Planning and Deterministic Compilation for Reliable Enterprise Text-to-SQL, Yi Ai, arXiv 2608.16663, 2026-08-17.)

This kit exists to make writing that kind of governed model tractable: one YAML contract, a linter that catches the disputes before an LLM ever sees them, compilers that keep the model useful across whichever standard a warehouse runs, and an eval harness that proves the gain on your own data rather than citing someone else's number.

Those figures are external. This repo's own eval numbers, further down, come from a deterministic fake model and are labeled as such.

Related MCP server: mcp-analytics

Demo

Real captured output, not illustrative. semkit validate against the Northgate Logistics example, which resolves every join and revenue ambiguity in its own warehouse on purpose:

$ uv run semkit validate examples/northgate_logistics/model.yaml
examples/northgate_logistics/model.yaml: 0 errors, 0 warnings

semkit query, the deterministic metric compiler, grouping net revenue by customer region for fiscal year 2025 (no LLM involved: this is a metric name, a group-by, and a filter turned into SQL by the join graph, then run against DuckDB):

$ uv run semkit query examples/northgate_logistics/model.yaml \
    --metric net_revenue --by customers.region --where "calendar.fiscal_year = 2025"
customers.region    net_revenue
------------------  -----------
Great Lakes         461,525.44
Gulf Coast          620,047.94
Mountain West       466,440.85
Northeast Corridor  653,194.43
Pacific Northwest   957,477.47

semkit compile turns the same model into each target standard. The Snowflake CREATE SEMANTIC VIEW output, trimmed (the header comment names the two non-preferred relationships left out because Snowflake has no preferred-path flag):

$ semkit compile examples/northgate_logistics/model.yaml --to snowflake-sql
CREATE OR REPLACE SEMANTIC VIEW northgate_logistics
  TABLES (
    customers AS main.customers PRIMARY KEY (customer_id) WITH SYNONYMS ('clients', 'accounts') COMMENT = 'Northgate''s billing customers. One row per customer account.',
    sites AS main.sites PRIMARY KEY (site_id) WITH SYNONYMS ('locations', 'facilities') COMMENT = 'Physical operating locations (terminals, rail yards, trucking lanes, warehouses) that belong to a customer.',
    contracts AS main.contracts PRIMARY KEY (contract_id) WITH SYNONYMS ('agreements') COMMENT = 'Service contracts between Northgate and a customer for a given site.',
    work_orders AS main.work_orders PRIMARY KEY (work_order_id) WITH SYNONYMS ('service orders', 'tickets') COMMENT = 'Individual work orders performed at a site.',
    invoices AS main.invoices PRIMARY KEY (invoice_id) WITH SYNONYMS ('bills') COMMENT = 'Customer invoices. Has two dates (invoice_date and posted_date) and a net total; see the invoice_lines entity for the gross line-item amounts.',
    invoice_lines AS main.invoice_lines PRIMARY KEY (invoice_line_id) WITH SYNONYMS ('line items') COMMENT = 'Line items on an invoice: gross service charges and credit adjustments. See invoices.invoice_amount for the net total after credits.',
    calendar AS main.calendar PRIMARY KEY (date) WITH SYNONYMS ('dates', 'fiscal calendar') COMMENT = 'Day-grain date dimension used to resolve fiscal calendar questions.',
    site_safety_monthly AS main.site_safety_monthly PRIMARY KEY (site_id, month) WITH SYNONYMS ('safety exposure') COMMENT = 'Pre-aggregated safety exposure at the (site, month) grain: incident counts and labor hours worked, already at the grain the incident rate metric needs so that joining raw incidents to raw work orders (which would fan out and inflate both counts) is never necessary.',
    employees_dim AS main.employees_dim PRIMARY KEY (employee_id) WITH SYNONYMS ('workforce', 'staff') COMMENT = 'Fictional, non-identifying workforce dimension (no names, no personal data) used for headcount-shaped questions. Not joined to any other entity in this example.'
  )
...

The other targets work the same way: --to snowflake-yaml, --to osi (validated against the vendored Apache Ossie schema), --to metricflow, and --to context-pack.

scripts/demo.py calls the MCP server the way a real client would (the SDK's in-memory Client, no subprocess): describes a metric, runs a governed query showing the SQL it used, explains why one join path was preferred over another, and searches for a term ("region") genuinely ambiguous across more than one entity:

{
  "metric": "net_revenue",
  "by": ["customers.region"],
  "sql": "SELECT customers.region AS \"customers.region\", SUM(invoices.invoice_amount) AS net_revenue FROM (...) AS invoices JOIN (...) AS customers ON invoices.customer_id = customers.customer_id GROUP BY customers.region ORDER BY customers.region LIMIT 5",
  "columns": ["customers.region", "net_revenue"],
  "rows": [["Great Lakes", 1514442.46], ["Gulf Coast", 2071366.76], ["Mountain West", 1719903.52]]
}

Full output, including describe_metric, explain_join, and the ambiguous search_semantics call, is in scripts/demo.py; run it to see all of it.

Architecture

flowchart LR
    YAML[model.yaml\nvendor-neutral contract]
    VALIDATE[semkit validate\nschema + lint rules]
    subgraph Compilers[semkit compile]
        SNOWYAML[snowflake-yaml]
        SNOWSQL[snowflake-sql]
        OSI[osi\nApache Ossie v1]
        MF[metricflow\ndbt semantic_models]
        CP[context-pack\nMarkdown + JSON]
    end
    QUERY[semkit query\ndeterministic metric compiler]
    DB[(DuckDB warehouse)]
    SERVE[semkit serve\nMCP server: tools,\nresources, one prompt]
    EVAL[semkit eval\nschema-only vs. context-pack\nvs. semantic-api]

    YAML --> VALIDATE
    VALIDATE --> Compilers
    VALIDATE --> QUERY
    QUERY --> DB
    SERVE --> QUERY
    SERVE -.->|semantic://context-pack| CP
    EVAL --> QUERY
    EVAL --> DB

Quick start

Takes under five minutes, no API key required.

git clone https://github.com/25andresbernal/semantic-model-kit.git
cd semantic-model-kit

export PATH="$HOME/.local/bin:$PATH"   # if uv is not already on PATH
uv venv --python 3.12
uv pip install -e ".[dev]"

# Build the example warehouse (deterministic, seeded, safe to publish; gitignored output)
uv run python examples/northgate_logistics/generate_data.py

# See it work end to end
uv run semkit validate examples/northgate_logistics/model.yaml
uv run semkit query examples/northgate_logistics/model.yaml --metric revenue
uv run python scripts/demo.py

# Run the test suite
uv run pytest

Connect the MCP server to a client

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "semantic-model-kit": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/semantic-model-kit",
                "semkit", "serve", "examples/northgate_logistics/model.yaml"]
    }
  }
}

Claude Code:

claude mcp add semantic-model-kit -- uv run --directory /absolute/path/to/semantic-model-kit \
  semkit serve examples/northgate_logistics/model.yaml

No environment variables are needed for the default stdio transport. Point --transport http at a real deployment and set SEMKIT_MCP_TOKEN (see Configuration below) instead.

Configuration

No configuration or API key is required for the quick start. semkit serve MODEL.yaml flags: --database PATH (DuckDB file query_metric runs against, defaults to warehouse.duckdb next to the model), --transport stdio|http (default stdio, no auth needed), --port N (default 8000, HTTP only). SEMKIT_MCP_TOKEN, required only for --transport http, is the bearer token clients must send as Authorization: Bearer <token>; the server refuses to start over HTTP without it.

How it works

  • model.py: Pydantic v2 models for the YAML contract, and the source of truth a JSON schema is exported from.

  • lint.py and graph.py: the rules behind semkit validate (every metric has an owner and a verified question, every relationship resolves, synonyms do not collide) and the join-path resolver they share with the query compiler, so a validated model cannot produce an ambiguous join later.

  • compiler/query.py: turns a metric name, a group-by list, and a filter list into one SQL statement against DuckDB, using that same join resolver.

  • serve/: the MCP server. server.py registers eight tools (list_entities, describe_entity, list_metrics, describe_metric, query_metric, search_semantics, explain_join, verified_questions), two resources (semantic://model, semantic://context-pack), and one prompt. context.py renders the context-pack resource; auth.py is the HTTP transport's bearer-token middleware. query_metric is the only tool that touches DuckDB, and it raises a tool error naming the actual problem (unreachable entity, ambiguous join, non-additive-dimension violation) rather than guessing.

  • cli.py: the semkit entry point (validate, query, serve, plus the compilers and eval command in docs/standards-comparison.md and docs/eval-method.md).

  • examples/northgate_logistics/: a fictional logistics company with a seeded data generator, a fully resolved semantic model, and 40 questions with gold SQL.

Design decisions

  • A vendor-neutral source model with compilers, instead of authoring in one vendor's format. Snowflake semantic views, Open Semantic Interchange, and dbt's MetricFlow each have their own YAML shape and their own gaps. Authoring once and compiling out means a model built for a DuckDB proof of concept is not thrown away the day the warehouse becomes Snowflake.

  • Preferred join paths are declared, not inferred. A join-inference heuristic is exactly the silent guess that produces a confidently wrong number when a warehouse offers more than one legitimate path between two tables. Marking one path preferred: true, and having semkit validate refuse an unresolved ambiguity, moves that decision to authoring time, where it is cheap to get right, instead of query time, where it is invisible.

  • A deterministic semantic query API sits beside free-form SQL, not in place of it. query_metric and semkit query only express what this model certifies. For those questions, the answer compiles the same way every time and shows its SQL, rather than being generated fresh, and inconsistently, by a model each time someone asks.

  • Verified questions double as the eval set. A separate test suite invites drift from the model's own documentation. Verified questions are both the regression test a PM checks before shipping a change and the exact input docs/eval-method.md's harness runs.

  • The committed eval numbers come from a fake model, and say so. A report scored by a real LLM would need a paid API key just to reproduce and would drift with every model version. The committed docs/EVAL-REPORT.md uses a deterministic FakeLLM so CI stays offline; the real accuracy claims above come from the two cited external benchmarks, not from this repo's own committed run.

Standards this compiles to

Snowflake semantic views, Apache Ossie (Open Semantic Interchange), and dbt MetricFlow, with Cube, LookML, and Databricks metric views compared alongside; what each captures and where the compile is lossy is in docs/standards-comparison.md.

What the eval measures

The eval runs the same 40 Northgate questions three ways and scores each answer by result-set equivalence against gold SQL on DuckDB. The numbers below come from this repo's committed run with FakeLLM, a deterministic stand-in that behaves like a competent but context-blind model. They demonstrate the harness and the failure classes, not any real model's accuracy; run with --provider anthropic or --provider openai-compatible and a key to measure a real one.

Mode

What the model receives

Accuracy

Correctly refused

Wrongly refused

Confident-wrong

schema-only

table and column names only

28% (11/40)

0

0

29

context-pack

schema plus the compiled context pack

82% (33/40)

4

0

3

semantic-api

metric and dimension catalog; kit compiles the SQL

52% (21/40)

4

12

3

Two things to read off that table. Confident-wrong answers, the failure the Snowflake engineering blog warns about, fall from 29 to 3 once the model has the context pack. And the semantic-api mode never picks a wrong join, date column, or amount column; its lower headline number is because 14 of the 40 questions have no declared metric, so it refuses them, which is the honest outcome for a governed interface and the reason the two modes belong together.

See docs/eval-method.md for how the three modes (schema-only, context-pack, semantic-api) are scored, and docs/EVAL-REPORT.md for the full per-question report.

Roadmap

  • A second, non-fake judge/LLM eval run against a real provider, published alongside the committed FakeLLM report rather than in place of it.

  • A refresh command that re-validates a model against a live warehouse's actual schema.

  • Pagination on query_metric and search_semantics for a model larger than one domain.

Contributing

Issues and pull requests are welcome. Before opening one: uv run ruff check ., uv run ruff format ., uv run pytest. If you change examples/northgate_logistics/generate_data.py, rerun it and confirm tests/test_query.py and tests/test_serve.py still pass against the regenerated warehouse.

License

MIT. See LICENSE.

Available Tools

8 tools
describe_entityA

Describe one entity in full detail.

Args: name: entity name, as returned by list_entities, e.g. "invoices".

Returns description, synonyms, source table, primary key, and every dimension, time dimension, fact, and filter declared on it. Raises a tool error naming the known entities if name does not exist.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It discloses the error behavior (raises a tool error naming known entities) and lists the return contents, which implies it is a read-only operation. It does not explicitly state that it has no side effects, but the 'describe' verb and return of metadata suggest a non-mutating operation. The error message detail is a positive addition.

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?

The description is compact and well-structured. It leads with the core purpose, then lays out args and returns in a clear format, and ends with error behavior. Every sentence adds value; there is no fluff or repetition. The structure makes it easy for an agent to parse quickly.

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

Completeness5/5

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

The description is complete for an entity-description tool. It lists all return components, explains the error behavior, and gives a concrete example. An output schema exists, so detailed return types are not required in the description. Nothing critical is missing for the agent to successfully invoke the tool.

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 coverage is 0%, so the description must fully explain the 'name' parameter. It does: 'entity name, as returned by list_entities, e.g. "invoices"' — this specifies the value's source, format, and an example. The description adds meaning far beyond the bare schema (which only says 'string'), fully compensating for the lack of schema documentation.

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 clearly states the tool's purpose: 'Describe one entity in full detail' and enumerates exactly what is returned (description, synonyms, source table, primary key, dimensions, facts, filters). This distinguishes it from sibling tools like list_entities (which lists entities) and describe_metric (which describes metrics), making its scope obvious.

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?

The description implicitly guides usage by specifying that the name should be 'as returned by list_entities', which tells the agent to first list entities to obtain a valid name. It also explains the error behavior for invalid names, which helps the agent handle failure modes. However, it does not explicitly state when to prefer this tool over describe_metric or other siblings, though the resource type (entity vs metric) makes this clear enough.

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

describe_metricA

Describe one metric in full detail.

Args: name: metric name, as returned by list_metrics, e.g. "revenue".

Returns description, SQL expression, filters applied, owner, synonyms, its time dimension (if any), any dimensions it is declared non_additive_over (grouping by these would produce a misleading result), and allowed_dimensions: every entity.field dimension reachable from the metric by exactly one unambiguous join, safe to pass to query_metric's by. Raises a tool error naming the known metrics if name does not exist.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.5/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden and does a good job: it warns that grouping by non_additive_over dimensions would produce misleading results, and it discloses the error behavior (raises a tool error naming known metrics if name does not exist). It does not explicitly say the operation is read-only, but 'describe' strongly implies it and no side effects are mentioned. This exceeds the baseline for a tool with zero annotations.

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?

The description is compact, front-loaded with the core purpose, and every sentence earns its place: the purpose, the parameter guidance, the return enumeration, the non_additive_over warning, and the error behavior. No fluff or repetition.

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

Completeness5/5

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

Given the tool's low complexity (one parameter), the presence of an output schema, and no annotations, the description is remarkably complete. It covers what the tool returns, where valid parameters come from, cross-references query_metric, discloses a subtle semantic trap (non_additive_over), and describes error handling. An agent can call this correctly without any further information.

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?

Schema coverage is 0% and there is a single parameter, but the description adds exactly the needed semantics: name is 'metric name, as returned by list_metrics, e.g. "revenue"', plus the error case. It wisely avoids a static enum because metric names are dynamic, and instead tells the agent where to get valid values. This is a model of parameter explanation for an open-ended string.

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 begins with a specific verb+resource statement: 'Describe one metric in full detail.' It then enumerates the exact fields returned (SQL expression, filters, owner, synonyms, time dimension, non_additive_over, allowed_dimensions), which distinguishes it clearly from sibling tools like list_metrics (lists all metrics) and query_metric (queries data), not just from describe_entity.

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?

The description gives practical usage guidance: the name parameter must come 'as returned by list_metrics', and allowed_dimensions are explicitly said to be 'safe to pass to query_metric's by'. This contextualizes the tool in an agent workflow. It does not explicitly state when to prefer this over describe_entity, but the metric-vs-entity distinction is self-evident from the name and opening sentence.

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

explain_joinA

Explain, in words, the preferred join path between two entities and what it avoided.

Args: from_entity: starting entity name, e.g. "invoices". to_entity: destination entity name, e.g. "customers".

Returns the chosen path described as a sequence of hops (or null with ambiguous: true and a note if no single path is fully marked preferred), plus every alternative path that was available and why it was not the one chosen.

ParametersJSON Schema
NameRequiredDescriptionDefault
to_entityYes
from_entityYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It does well by detailing the return behavior: it returns the chosen path as a sequence of hops, and in cases where no single path is preferred, it returns null with an 'ambiguous: true' flag and a note, as well as listing every alternative path and the reason it was not chosen. This gives the agent a clear expectation of outcomes. It does not explicitly state that the tool is read-only, but the verb 'explain' and the nature of the task make that implicit. It also does not mention any side effects, which is appropriate for a query-style tool.

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?

The description is well-structured with sections for args and returns, and it is not overly verbose. It front-loads the main purpose in the first sentence. The inclusion of examples and the detailed return behavior is valuable, though it could be slightly trimmed without losing meaning. Overall, it is efficient and earns each sentence's place.

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?

The tool has an output schema, so the return structure is partially defined. The description adds key context beyond that: the meaning of 'ambiguous' and the fact that alternatives are also returned with reasons for non-selection. It does not mention that entity names must correspond to known entities (e.g., from list_entities), but this is derivable from the example and the nature of the tool. Given the moderate complexity (2 params, no nested objects), the description covers what an agent needs to invoke and interpret the tool correctly.

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?

Because schema description coverage is 0%, the description must clarify the parameters, and it does so excellently. It defines 'from_entity' as the starting entity name with a concrete example ('invoices') and 'to_entity' as the destination entity name with an example ('customers'). This goes beyond the bare schema by providing real-world usage examples, making it clear what values are expected and how they are used.

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 clearly states the tool's purpose: to explain the preferred join path between two entities in words, including what it avoided. It names the resource (join path) and the specific verb (explain), and it distinguishes itself from sibling tools like query_metric or describe_entity by focusing on the join path explanation rather than data retrieval or entity metadata. The scope is precise and unambiguous.

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?

The description implies usage when one needs to understand how two entities relate, but it does not explicitly state when to use this tool versus alternatives. There is no 'use this when...' or 'instead of...' guidance. However, since no sibling tool is directly comparable (list_entities, describe_entity, etc. serve different purposes), the context is clear by implication. It lacks explicit exclusions or alternative routing.

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

list_entitiesA

List every entity (table) in the semantic model with its description and synonyms.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior3/5

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

With no annotations, the description carries the behavioral disclosure burden. It indicates a read-only listing operation and specifies the returned contents, but does not mention pagination, ordering, or behavior for entities without descriptions or synonyms. Auth requirements and potential rate limits are also omitted.

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?

The description is a single, efficient sentence that leads with the primary action and resource. It contains no filler or redundant information, making it easy to scan and understand.

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

Completeness5/5

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

The description is complete for a parameterless list tool with an output schema provided. It clearly states the return contents and scope; the output schema handles detailed return structure, so no further explanation is needed.

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?

There are no parameters, so the description does not need to explain parameter meanings. The input schema is empty and fully covered, and the description adds context about the output contents, which is sufficient for a parameterless tool.

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 clearly states the action (list), the resource (entities/tables), the scope (semantic model), and the included fields (description and synonyms). It differentiates from list_metrics by specifying the resource type, making the tool's purpose immediately identifiable.

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?

The description explains what the tool does but provides no explicit guidance on when to use it versus siblings like describe_entity or search_semantics. The use case is implied by the name and description, but no alternatives or exclusions are mentioned.

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

list_metricsA

List every certified metric with its description, owner, and synonyms.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior3/5

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

With no annotations, the description must carry behavioral context; it does indicate a read-only enumeration of all certified metrics, but it does not disclose pagination, volume, filtering semantics, or what 'certified' means. This is adequate for a simple list operation but leaves some behavioral details unstated.

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 compact sentence with the action front-loaded and the returned fields listed concisely. There is no filler or redundant information.

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?

For a zero-parameter tool with an output schema, the description is largely complete: it states the scope and returned fields. It could briefly clarify when to use a sibling tool or define 'certified', but nothing essential for invoking the tool is missing.

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 has zero parameters, so there is no parameter ambiguity to document. The baseline of 4 applies because no parameter semantics are needed.

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 uses a specific verb ('List') with a clear resource ('every certified metric') and enumerates the returned fields ('description, owner, and synonyms'). This distinguishes it from siblings like describe_metric (singular) and list_entities (different resource).

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?

The wording implies this is the tool for obtaining the full catalog of certified metrics, but it does not explicitly state when to prefer alternatives such as describe_metric or query_metric. There are no exclusions or when-not-to-use notes, so guidance is only implicit.

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

query_metricA

Compile a metric query and run it against the model's DuckDB warehouse.

Args: metric: metric name, e.g. "revenue". by: dimensions to group by, each as "entity.field", e.g. ["customers.region"]. See describe_metric's allowed_dimensions for what is safe to pass here. where: filter clauses, each as "entity.field op value", e.g. ["calendar.fiscal_year = 2025"]. limit: maximum rows to return (default 100).

Returns columns, rows, and the exact SQL that was run, so the caller can show its work rather than just asserting a number. Raises a tool error, never a guess, if a dimension is unknown, a join between two required entities is ambiguous or unreachable, or the metric is declared non_additive_over a dimension in by.

ParametersJSON Schema
NameRequiredDescriptionDefault
byNo
limitNo
whereNo
metricYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It does well by stating what the tool returns (columns, rows, exact SQL) and explicitly enumerating error conditions: unknown dimensions, ambiguous/unreachable joins, and non-additive dimensions. It could add an explicit read-only/no-mutation statement, but for a query tool the return and error behavior is well covered.

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?

The description is front-loaded with the core purpose and then organized into Args, Returns, and Raises sections. Each sentence adds functional value: parameter formats, examples, default behavior, and error semantics. There is no filler or redundancy.

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

Completeness5/5

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

For a 4-parameter tool with an output schema and no annotations, the description is complete. It explains all input formats, the return shape, default limit, and failure modes, and even relates to sibling describe_metric for what dimensions are safe. Nothing essential for calling the tool correctly is missing.

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 compensate for the bare input schema. It does: metric is exemplified, by is given with entity.field format and a concrete example plus a pointer to describe_metric, where is given with entity.field op value format and an example, and limit is documented with its default. Every parameter receives meaning beyond the schema.

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 opens with a specific verb and resource: 'Compile a metric query and run it against the model's DuckDB warehouse.' It clearly identifies the tool as executing metric queries, which distinguishes it from sibling tools that list or describe entities/metrics or explain joins.

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?

The description gives clear context for when to use the tool: to query a metric with grouping and filtering. It also points the caller to describe_metric for allowed_dimensions, which serves as indirect guidance on using a sibling tool for safe inputs. It stops short of explicitly stating when not to use query_metric or naming alternatives for other scenarios.

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

search_semanticsA

Search entity, dimension, time dimension, fact, filter, and metric names, descriptions, and synonyms for a free-text term.

Args: text: free-text search, e.g. "region" or "revenue". Case-insensitive substring match.

Returns every match, tagged with kind (entity, dimension, time_dimension, fact, filter, or metric) and the owning entity when there is one, so an agent can tell whether a term like "region" resolves to more than one place before guessing which is meant.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

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

There are no annotations, so the description carries the behavioral disclosure burden. It does this well by stating case-insensitive substring matching, returning every match, and tagging each result with kind and owning entity. It does not mention empty-result behavior or result limits, but the core behavior is transparent.

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?

Three short sections with no filler: purpose, Args, and Returns. Every sentence earns its place, and the most important information is front-loaded.

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

Completeness5/5

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

For a one-parameter search tool with an output schema, this covers the input semantics, matching behavior, return structure, and the reason the return shape matters. No crucial invocation detail is missing.

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%, but the description fully compensates. It defines the sole parameter 'text' as a free-text search with examples and matching semantics, adding meaning the schema alone lacks.

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 opens with a specific verb ('Search') and enumerates the full scope: entity, dimension, time dimension, fact, filter, and metric names, descriptions, and synonyms. This distinguishes it from siblings like list_entities and describe_metric, which target a single resource type.

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?

The description gives clear context: use this for free-text, case-insensitive substring lookup across semantic objects, and it explicitly frames why it matters (disambiguating terms like 'region' before guessing). It does not explicitly name sibling alternatives or state when not to use it, so it stops short of a 5.

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

verified_questionsA

List this model's verified questions: known-good question, metric, dimensions, and the gold SQL each one is checked against. These are the model's own regression tests.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

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

No annotations are provided, so the description carries the full behavioral burden. The verb 'List' conveys a read-only, non-destructive operation, and the description discloses what the output contains (question, metric, dimensions, gold SQL) and the purpose (regression tests). It does not discuss edge behaviors like staleness or refresh behavior, but for a zero-parameter list tool this is a minor gap.

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?

Two tight sentences with no filler. The first sentence front-loads the action and content, and the second adds the key interpretive fact (regression tests) that differentiates it from sibling tools. Every word earns its place.

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

Completeness5/5

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

For a zero-parameter list tool with an output schema present, the description is complete: it tells the agent what is returned (verified questions with metric, dimensions, gold SQL) and why the list exists (the model's regression tests). Nothing an agent needs to invoke it correctly is missing.

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 has zero parameters and 100% schema coverage of an empty schema, so there is nothing for the description to add about parameters. Baseline 4 applies because there are no parameters whose meaning could be under-specified.

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 uses a specific verb ('List'), names the resource ('this model's verified questions'), and specifies the contents ('known-good question, metric, dimensions, and the gold SQL'). It also distinguishes itself from the sibling catalog tools by labeling these as 'the model's own regression tests,' so an agent can tell it apart from list_entities and list_metrics without opening schemas.

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?

The description supplies clear context for when this tool is relevant—when one wants to inspect the model's regression/test questions rather than the data catalog—via 'These are the model's own regression tests.' It does not explicitly name alternatives or state when not to use it, but the contrast with siblings (list_entities, list_metrics, describe_*) is reasonably implied by describing the resource as the model's own tests.

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. 8 tool updatesv0.1.0
    • First observeddescribe_entity
    • First observeddescribe_metric
    • First observedexplain_join
    • First observedlist_entities
    • First observedlist_metrics
    • First observedquery_metric
    • First observedsearch_semantics
    • First observedverified_questions

TDQS

A4.4/5.0

Scored across 8 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: listing entities/metrics, describing them in detail, querying metrics, free-text searching, explaining join paths, and retrieving verified questions. There is no overlap or ambiguity between tools; an agent can reliably select the right one for a given task.

Naming Consistency5/5

All tool names follow a consistent verb_noun (or verb_noun_noun) pattern in snake_case, such as list_entities, describe_entity, query_metric, and explain_join. The naming is uniform and predictable, with no mixed conventions or vague verbs.

Tool Count5/5

With 8 tools, the server is well-scoped for its purpose of exploring and querying a semantic model. Each tool serves a necessary function—discovery, description, querying, search, join explanation, and regression testing—and none feels redundant or missing.

Completeness5/5

The tool surface fully covers the domain of semantic model interaction: listing and describing entities and metrics, querying metrics with validation, searching across all semantic elements, explaining join paths, and checking verified questions. There are no obvious gaps; even potential needs like filtering dimensions are handled within query_metric and describe_entity.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables governed, agent-agnostic data exploration by allowing users to ask natural language questions through MCP-compatible agents, executing safe, permission-scoped queries against data sources and returning interactive charts.
    16 npm
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables enterprise AI agents to query governed data lineage, PII-aware schema documentation, and semantic metadata from SQL logs via MCP, with role-based access and vector search.
    MIT