Skip to main content
Glama

semantic-mcp

Uma camada semântica governada, exposta a agentes de IA via MCP

O agente escolhe métricas de um vocabulário fechado. Nunca escreve SQL.

Python dbt DuckDB MCP Testes Cobertura Rejeição

A tese • Como rodar • Resultados • Arquitetura • SDD • Limitações


A tese

Times de dados estão conectando agentes de IA direto ao warehouse. O padrão dominante é text-to-SQL: o modelo recebe o schema, escreve SQL, o banco executa.

Isso falha de três formas, e as três são invisíveis para quem perguntou.

Alucinação de schema. O modelo referencia uma coluna que não existe — ou que existe com outro significado. No melhor caso, erro. No pior, a query roda e devolve um número plausível e errado.

Divergência de definição. receita calculada de três formas em três conversas. O modelo não sabe que a empresa exclui frete, porque isso não está no schema — está na cabeça de alguém.

Superfície de risco. SQL arbitrário, gerado por modelo, executado no warehouse, sem revisão.

O problema não é que o modelo seja ruim. É que estamos pedindo a ele a coisa errada.

Este projeto inverte a fronteira. O agente não recebe um schema — recebe um vocabulário: métricas e dimensões declaradas por um analytics engineer, com descrição de negócio e notas de quando usar e quando não usar. Ele monta uma chamada tipada. O servidor compila o SQL.

Alucinação de coluna deixa de ser improvável e passa a ser impossível por construção: nenhum nome de coluna atravessa a fronteira.

O custo é expressividade — perguntas fora do contrato são recusadas. A aposta é que recusar é melhor que aproximar, e que a forma de cobrir mais perguntas é estender o contrato, um ato deliberado de modelagem, em vez de afrouxar a fronteira.

Related MCP server: sql-steward

Como rodar

Sem credencial, sem conta em nuvem, sem chave de API.

git clone https://github.com/stefbartieri/semantic-mcp
cd semantic-mcp
pip install -r requirements.txt
make demo

make demo gera os dados, materializa o warehouse com dbt, roda os testes e imprime o relatório de avaliação. Leva menos de um minuto.

Para usar como servidor MCP:

make serve          # stdio
// claude_desktop_config.json
{
  "mcpServers": {
    "semantic": {
      "command": "python",
      "args": ["-m", "semantic_mcp.server"],
      "cwd": "/caminho/para/semantic-mcp",
      "env": { "PYTHONPATH": "src" }
    }
  }
}

Resultados

  Avaliação — modo deterministic
  ----------------------------------------------------------
  Resolução     30/30   100.0%   CA2 >= 90%   [PASS]
  Rejeição      10/10   100.0%   CA3 == 100%  [PASS]
  Correção       7/7    100.0%   CA4 diff 0   [PASS]
  ----------------------------------------------------------
  Código exato do erro: 10/10

Três eixos, porque acertar mais perguntas não vale nada se a fronteira vazar:

Eixo

O que mede

Resolução

A pergunta de negócio vira a chamada de ferramenta correta?

Rejeição

As 10 perguntas adversariais são recusadas, com o código de erro certo?

Correção

O SQL compilado bate com uma query de referência escrita à mão? Diferença esperada: exatamente 0.

Regra da constituição do projeto: uma melhora na resolução que piore a rejeição não é uma melhora.

O que os casos adversariais exercitam

Caso

Classe de ataque

a01

Métrica inexistente, mas plausível no domínio (margem_bruta)

a04

Coluna real do banco, fora do contrato (preco_unitario)

a05

Injeção de SQL no valor do filtro

a06

Injeção de SQL no nome do campo

a08

Operador não declarado (like)

a10

Campo existe no contrato, mas não é permitido nesta métrica

Nenhum deles chega ao warehouse. Todos voltam como erro estruturado com o vocabulário válido — para o agente se corrigir na próxima chamada, em vez de tentar de novo ao acaso:

{
  "error": "unknown_metric",
  "message": "A métrica 'receta' não existe no contrato.",
  "did_you_mean": ["receita_liquida"],
  "available": ["clientes_ativos", "itens_vendidos", "pedidos",
                "receita_liquida", "taxa_cancelamento", "ticket_medio"]
}

Arquitetura

                        FRONTEIRA DE CONFIANÇA
                                 │
   Agente de IA                  │              Sistema governado
   (não confiável)               │              (determinístico)
                                 │
  pergunta em PT                 │
        │                        │
        ▼                        │
  ┌───────────┐   chamada tipada │   ┌──────────────┐
  │  resolver │ ────────────────────▶│  guardrails  │
  └───────────┘  {metric,dims,    │   └──────┬───────┘
                  filters,range}  │          │ tudo validado
                                  │          │ contra o contrato
                                  │          ▼
                                  │   ┌──────────────┐
                                  │   │   compiler   │
                                  │   └──────┬───────┘
                                  │          │ SQL parametrizado
                                  │          ▼
                                  │   ┌──────────────┐
                                  │   │  warehouse   │  DuckDB (read-only)
                                  │   └──────┬───────┘   ▲
                                  │          │           │ dbt build
                                  │          ▼        ┌──┴─────┐
                                  │  rows + SQL + def │ marts  │
                                  │                   └────────┘

Nenhuma string escrita pelo agente chega ao warehouse. O que atravessa a fronteira é um conjunto de identificadores que só existem se estiverem no contrato.

As 4 ferramentas MCP

Ferramenta

Para quê

list_metrics

O vocabulário completo. O agente começa por aqui.

describe_metric

Definição, expressão, grão, e as notas de quando usar / quando não usar.

query_metric

Executa. Devolve as linhas e o SQL compilado, para auditoria.

explain_lineage

Cadeia de modelos dbt do source ao mart, lida do manifest.json.

O contrato é o produto

- name: ticket_medio
  label: Ticket médio
  expr: "SUM(f.item_total - f.item_desconto) / NULLIF(COUNT(DISTINCT f.order_id), 0)"
  dimensions: [mes, canal, categoria, regiao, segmento]
  when_to_use: >
    Use para comparar o valor típico de compra entre canais, regiões ou períodos.
  when_not_to_use: >
    Não recorte por categoria. O denominador conta o pedido inteiro, mas o
    numerador só os itens daquela categoria — o resultado não tem significado.

A descrição é o prompt. É literalmente o que o agente lê para decidir. Por isso when_not_to_use é campo obrigatório: o contrato não carrega só o cálculo, carrega o julgamento de quem modelou.

E quando o agente cai numa dessas armadilhas, o servidor não recusa — ele entrega o número com o aviso, porque o número existe, só engana:

"warnings": ["Ticket médio recortado por categoria mistura grãos: o denominador
  conta o pedido inteiro e o numerador só os itens da categoria."]

Como este projeto foi especificado

Construído com Spec-Driven Development. A ordem foi constituição → spec → plano → tarefas → código, e os artefatos estão versionados:

Documento

O que fixa

.specify/memory/constitution.md

7 princípios inegociáveis. Requisito que conflita com princípio perde.

specs/001-semantic-layer/spec.md

Problema, hipótese, escopo, RF/RNF, critérios de aceitação, riscos

specs/001-semantic-layer/plan.md

Arquitetura, decisões técnicas com alternativa descartada, rastreabilidade

specs/001-semantic-layer/tasks.md

24 tarefas, cada uma com critério de pronto verificável

O plano inclui uma seção "Como sei que falhei" — sinais que invalidariam a hipótese. Um deles se confirmou; está em Limitações, logo abaixo.

Limitações

Declaradas, não escondidas.

O resolver determinístico acerta 100%, e isso é um resultado ruim. O plano previa que um baseline de regras acertando quase tudo significaria uma suíte fácil demais para discriminar. Foi o que aconteceu: as perguntas de avaliação usam o vocabulário do próprio contrato, então casamento por sinônimo resolve. Os números de resolução medem a cobertura do contrato, não inteligência. Os de rejeição e correção continuam valendo — esses testam a fronteira.

40 casos são poucos para conclusão estatística. A suíte é extensível por YAML; o número honesto aqui é "nenhum vazamento em 10 classes de ataque", não "seguro".

Sem cobertura temporal relativa. "Últimos 3 meses" não resolve — só recorte por mês. É limitação do contrato, não da arquitetura.

DuckDB local não é um warehouse de produção. O compilador gera SQL ANSI e o dialeto está isolado em warehouse.py, mas concorrência, custo de query e permissão por linha não foram exercitados.

O modo LLM existe mas não foi avaliado em escala — precisa de ANTHROPIC_API_KEY e ficou fora do caminho padrão por decisão de reprodutibilidade (Princípio V).

Estrutura

.specify/memory/constitution.md   princípios do projeto
specs/001-semantic-layer/         spec, plano, tarefas
semantic/contract.yml             o contrato — fonte única de verdade
dbt/                              staging + marts, 30 testes dbt
src/semantic_mcp/
  contract.py                     carga e validação do contrato
  guardrails.py                   validação da requisição (a fronteira)
  compiler.py                     requisição tipada -> SQL parametrizado
  warehouse.py                    DuckDB, read-only, binding
  service.py                      lógica das 4 ferramentas
  server.py                       servidor MCP (stdio)
  resolver.py                     baseline determinístico PT -> chamada
evals/
  cases.yaml                      30 válidos + 10 adversariais
  runner.py                       resolução / rejeição / correção
  reference.sql                   queries de conferência escritas à mão
tests/                            89 testes

Licença

MIT — veja LICENSE.

Available Tools

4 tools
describe_metricA

Definição completa de uma métrica: expressão, grão, dimensões e filtros permitidos, e as notas de quando usar e quando NÃO usar. Consulte antes de montar um recorte incomum.

ParametersJSON Schema
NameRequiredDescriptionDefault
metricYesNome canônico da métrica.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.5/5.0
Behavior3/5

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

No annotations are provided, so the description carries the burden. It implies a read-only lookup (a definition, not an execution) and discloses what the payload contains, but says nothing about permissions, caching, or lookup failure behavior. Output schema covers the return shape, so this is the acceptable minimum.

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?

A single front-loaded sentence that states what the tool returns first and the consult trigger last. No filler, though the enumeration is slightly dense.

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 one documented parameter and an existing output schema, the description needs only to convey purpose and the consult trigger, which it does. An agent has enough to call it correctly; only clearer routing against query_metric is missing.

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

Parameters3/5

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

Schema description coverage is 100% with a single well-named "metric" parameter ("Nome canônico da métrica"). The description adds no format or canonicalization guidance beyond that, so the baseline 3 applies.

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?

Starts with a specific verb+resource ("Definição completa de uma métrica") and enumerates the payload it delivers: expression, grain, dimensions, allowed filters, and usage notes. This clearly separates it from query_metric and list_metrics, though no sibling is named explicitly.

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?

"Consulte antes de montar um recorte incomum" gives one loosely-stated situation to reach for this tool, but the conditions are vague (what counts as "incomum") and no alternative (e.g. query_metric) is referenced for the common case. The "quando usar/quando NÃO usar" phrasing describes output content, not tool-selection guidance.

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

explain_lineageA

Cadeia de modelos dbt que produz a métrica, do source ao mart. Use para responder de onde vem o número.

ParametersJSON Schema
NameRequiredDescriptionDefault
metricYesNome canônico da métrica.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior2/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. It implies a read-only traversal but never states the operation is non-mutating, nor what happens for an unknown or non-existent metric, nor any permission requirements. Behavior beyond the conceptual output shape is largely undisclosed.

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 short sentences, both earning their place: the first defines the resource and its boundary, the second states the use case. Purpose is front-loaded with zero filler.

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 one-parameter read tool with an output schema (so return values need no prose) and full schema coverage, the description covers purpose and trigger adequately. Only the silence on failure/edge behavior with no annotations keeps it from being fully complete.

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

Parameters3/5

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

Schema description coverage is 100% for the single `metric` parameter, which already documents it as the canonical metric name. The description adds no naming convention, format, or lookup guidance beyond that, so baseline 3 applies.

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?

Names a specific resource and scope: the chain of dbt models producing a metric, from source to mart. This is clearly distinct from query_metric (returns numbers) and describe_metric (definition), though it never names those siblings explicitly.

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?

"Use para responder de onde vem o número" gives a clear, concrete usage context — answering provenance questions about a metric. No exclusions or named alternatives are offered, but the trigger condition is unambiguous.

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

list_metricsA

Lista todas as métricas do contrato semântico, com rótulo, descrição de negócio e dimensões aplicáveis. Comece por aqui: é o vocabulário completo que você pode usar.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.9/5.0
Behavior3/5

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

No annotations are provided, so the description carries the full burden. It is a zero-parameter list operation and 'Lista' implies a safe read, but the description never states that it is read-only or non-mutating, nor does it mention any auth or result-size behavior. It does add useful content context by naming the fields returned.

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 short sentences, zero waste, with the purpose front-loaded before the 'start here' guidance. Every clause earns its 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?

For a zero-param discovery tool with an output schema already present, the description is nearly complete; it need not explain return values. A small gap is the absence of any note about ordering, volume, or whether results are paginated, but nothing critical to correct invocation 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 takes zero parameters, so there is nothing to disambiguate and the baseline is 4. The description correctly refrains from inventing parameters and instead describes the output shape.

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 ('Lista todas as métricas do contrato semântico') and enumerates what each entry carries (rótulo, descrição de negócio, dimensões aplicáveis). It does not explicitly name the siblings (describe_metric, query_metric), but its 'start here / complete vocabulary' framing does implicitly separate it as the discovery entry point.

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?

'Comece por aqui' gives clear positive guidance on when to reach for this tool first, positioning it as the vocabulary-discovery step before drilling into a specific metric. It stops short of naming the alternative siblings or stating when not to use it.

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

query_metricA

Executa uma métrica com recortes e filtros opcionais. NÃO aceita SQL: informe apenas nomes declarados no contrato. Devolve as linhas, a definição da métrica e o SQL compilado, para auditoria.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMáximo de linhas.
metricYesNome canônico da métrica.
filtersNoLista de {field, operator, value}. Operadores: eq, neq, in. Apenas campos declarados no contrato.
dimensionsNoDimensões de recorte, do contrato.

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 present, the description carries the behavioral burden and does disclose key traits: the SQL rejection rule, the contract-only naming constraint, and the shape of the result (rows, metric definition, compiled SQL for auditing). It omits permission/auth or rate-limit behavior, but the input/output contract is well surfaced.

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 sentences, front-loaded with the action and immediately followed by the critical constraint and the return contract. No filler; every sentence earns its 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?

An output schema exists so return values need not be re-explained, yet the description helpfully names them. It conveys the core constraint (no SQL, contract names only) for a 4-parameter tool. Minor gap: no guidance on how to obtain valid metric/dimension/filter field names within the contract.

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

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents metric, filters, dimensions and limit with defaults and constraints (maxItems 3, max 10000). The description only reinforces the 'contract-declared names only' rule, adding no parameter-level detail beyond the schema baseline.

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?

States a specific verb+resource ('Executa uma métrica com recortes e filtros opcionais') and clearly separates this tool from siblings like list_metrics/describe_metric by emphasizing execution with dimensions/filters rather than listing or describing metadata.

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?

Gives a clear negative constraint ('NÃO aceita SQL: informe apenas nomes declarados no contrato'), which is essential guidance for invocation. It stops short of explicitly routing to siblings (e.g. use describe_metric first to discover names), so it is strong context but not full when/when-not coverage.

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 updatesv1.0.0
    • First observeddescribe_metric
    • First observedexplain_lineage
    • First observedlist_metrics
    • First observedquery_metric

TDQS

A4/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct role: list_metrics enumerates the vocabulary, describe_metric gives the full contract for one metric, query_metric executes it, and explain_lineage traces its dbt provenance. The list-vs-describe overlap is mitigated by the descriptions specifying different granularity and by explicit guidance to start with list_metrics.

Naming Consistency5/5

All four tools follow a uniform verb_noun snake_case pattern (list_metrics, describe_metric, query_metric, explain_lineage). The convention is predictable and readable throughout, with no mixing of styles.

Tool Count5/5

Four tools is well-scoped for a semantic-layer contract server: discovery, definition, execution, and lineage each earn their place. There is no redundant or filler tool.

Completeness4/5

The surface covers the full read lifecycle of a metric (discover, inspect, query, trace lineage), which is appropriate for a read-only semantic contract. A minor gap is the lack of a way to enumerate valid dimension/filter values (e.g. distinct dimension members) to help build recortes.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to understand and query your database safely by providing a semantic layer of metadata, with tools to search, explain, validate, and generate safe SQL.
    2
    MIT
  • A
    license
    A
    quality
    F
    maintenance
    A governed SQL gateway that exposes typed tools to AI agents, compiling safe read-only queries from a semantic layer while blocking PII before execution, supporting SQL Server, Postgres, and SQLite.
    9
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Exposes a governed semantic layer built on dbt Core and DuckDB, enabling AI agents to query predefined metric definitions for a P&C insurance dataset. Prevents metric hallucination by restricting agents to governed tools and read-only data access.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to query governed data warehouses through plain language, returning grounded answers with SQL, confidence grades, and signed receipts while enforcing access, testing, and audit controls.
    15
    Apache 2.0