Saltar para o conteúdo
BeryX
Voltar às respostas para RFP

Documentação do BeryX

Servidor MCP do BeryX

O BeryX expõe a qualidade de dados a agentes IA via Model Context Protocol (Claude Desktop, Claude Code, ou qualquer cliente MCP). O servidor é um bridge para a API HTTP — autentica-se como um utilizador normal (workspace e RBAC respeitados) e é read-only sobre as fontes; a única ação com efeitos é pedir um diagnóstico RCA (cria um relatório IA).

Tools disponíveis

Tool O que faz
list_datasets Datasets do workspace com linhas, tamanho e score
get_dataset Detalhe: colunas, issues abertos, sugestões
list_issues Issues com filtros (dataset, severidade, estado); os que têm tickets externos ligados trazem tickets (chave + estado)
list_suggestions Sugestões de melhoria (heurísticas + IA)
get_quality_overview Risco, exposição €/mês e totais FinOps
get_verdict Veredito para gates de CI («posso publicar?»): pass/warn/fail/unknown
get_dataset_context Contexto unificado de um dataset numa só chamada (ver abaixo); sections opcional
get_trust_decision Decisão de confiança antes de consumir, por finalidade (agente/rag/treino/bi): trusted/caution/untrusted/unknown, razões, required_actions e ttl_seconds/valid_until — ver TRUST_API.md. Cada chamada fica na auditoria (trust_decision_checked)
get_trust_facts Factos de confiança do dataset num envelope estável (schema_version 1): veredito com política, decisão agente, score e tendência 7d, contrato, política, contagens de sensibilidade, AI-readiness, freshness, ownership, linhagem, issues e links. detail=minimal (omissão, sem nomes de colunas) ou full — ver api/trust-facts.md
list_domains Data products (domínios) com owner, score e attainment do SLA no mês
get_domain_readiness Readiness de um data product por caso de uso (rag/ml/agente/bi): ready/ready_with_warnings/not_ready com bloqueadores e avisos — ver abaixo
get_domain_verdict Veredito de um data product para gates de CI («posso publicar o domínio?»): pass/warn/fail/unknown agregado dos datasets, com counts e o veredito de cada dataset; with_policy (omissão true) conta a política de dados. Mesmo JSON de GET /api/v1/domains/{id}/verdict (cartão #148)
match_datasets Última correspondência de entidades entre dois datasets (ex.: clientes do CRM × clientes do ERP): linhas de cada lado, pares, sem par, ambíguas 1→N, cobertura %, chaves usadas e a sobrevivência proposta (nunca aplicada). other_dataset_id opcional. Só lê o último run concluído, não lança correspondências (pedem-se em Duplicados › Entre datasets) e não devolve valores dos registos. Plano Team+
diagnose_issue Pede RCA de um issue (devolve report_id) — só em stdio
search_knowledge Pesquisa a base de conhecimento do workspace (resumos de relatórios e RCAs, resoluções de issues, decisões e notas): filtros query, kind e dataset_id; até 10 entradas, sem as obsoletas
get_report Estado/conteúdo Markdown de um relatório IA
get_failing_rows_sql Query SQL que lista as linhas que falham uma regra (rule_id), no dialeto da fonte ou noutro (dialect: postgres, mysql, mssql, bigquery, snowflake, databricks, sqlite), com os nomes citados e um limite (limit, omissão 100). Só devolve o texto: o MCP nunca a executa. Regra sem tradução por linha (ex.: row_count_min): sql a null, com reason e message. Lê GET /api/v1/rules/{id}/failing-rows.sql (cartão #400)
get_lineage Linhagem de um dataset («o que alimenta X?»): upstream e/ou downstream (direction) até hops saltos (1 a 3), cada dataset com o caminho no sentido do fluxo (path: «A → B → C»), as arestas com a origem (kinds: declared, dbt, openlineage, parsed, inferred), as colunas de ligação e o mapa de colunas quando existe, e os exposures a jusante. Lê GET /api/v1/datasets/{id}/lineage/traverse (cartão #173)
impact_of O que parte se mudar um dataset ou uma coluna (column_name opcional): datasets, exposures e destinos fora do warehouse a jusante e, com a coluna, as colunas que derivam dela (columns, só com linhagem de colunas). Datasets e colunas até 3 saltos (max_depth), exposures até 6 (exposure_max_depth), como na análise do impacto de um issue. Lê GET /api/v1/datasets/{id}/lineage/impact
path_between Caminho mais curto entre dois datasets (até 6 saltos): path, path_text («A → B → C») e as arestas; sem caminho de A para B procura de B para A (direction: target_to_source). Lê GET /api/v1/datasets/{id}/lineage/path
find_datasets Pesquisa composta: query, layer (bronze/silver/gold), domain (id ou nome), has_pii, min_score e usage_class (hot/active/cold/dead) aplicados em conjunto; devolve camada, score, classe de uso e as colunas com PII (só nomes). A camada, o PII e o score filtram os primeiros 500 datasets que cumprem o texto, o domínio e a classe de uso; com mais, truncated: true e o total só conta esses 500. Lê GET /api/v1/datasets/find

As quatro tools de travessia (cartão #173) são determinísticas: não chamam nenhum LLM, por isso funcionam com BYOK, com a IA desligada e sem chave. O chat do assistente usa as mesmas funções (backend/app/domain/lineage/traversal.py) e os mesmos resumos. Tetos: 40 datasets e 60 arestas (truncated: true quando cortam), só nomes — nunca o owner, o email nem o URL de um exposure. Um dataset de um domínio restrito sem acesso aparece como «Dataset restrito», com um id opaco (restrito-N) em vez do real e sem colunas. Uma aresta com uma ponta noutro workspace é ignorada.

Resources (read-only, application/json)

URI Conteúdo
quality://overview Mesmo resumo de get_quality_overview
datasets://catalog Catálogo de datasets (mesmo formato de list_datasets)
datasets://{dataset_id} Detalhe de um dataset (mesmo formato de get_dataset)
datasets://{dataset_id}/verdict Veredito de CI do dataset
datasets://{dataset_id}/lineage Linhagem direta do dataset (dataset-lineage): o resultado de get_lineage com 1 salto nos dois sentidos (cartão #173)
datasets://{dataset_id}/context Contexto unificado do dataset (mesmo formato de get_dataset_context)
datasets://{dataset_id}/trust-decision Decisão de confiança para a finalidade agente (mesmo JSON da API e de get_trust_decision)
datasets://{dataset_id}/trust-decision/{purpose} Aptidão do dataset no ponto de consumo para purpose (agente/rag/treino/bi; ml = treino): a mesma decisão, com required_actions e valid_until (cartão #151)
datasets://{dataset_id}/trust-facts Factos de confiança do dataset (dataset-trust-facts, detail=minimal): o mesmo JSON da API e de get_trust_facts (cartão #153)
domains://catalog Data products do workspace: id, name, owner, score, attainment_current_month_pct (mesmo formato de list_domains)
glossary://terms Glossário de negócio (glossary, plano Team+): termos com definição, sinónimos, as colunas aprovadas e a qualidade herdada delas — score, open_issues_total e explanation (cartão #169)
domains://{domain_id}/readiness Readiness do data product para bi (mesmo JSON da API e de get_domain_readiness)
domains://{domain_id}/readiness?use_case=rag Readiness para outro caso de uso (rag/ml/agente/bi; treino = ml)
domains://{domain_id}/verdict Veredito de CI do data product, com a política de dados (domain-verdict): o mesmo JSON da API e de get_domain_verdict (cartão #148)

Contexto unificado por dataset (cartão #146)

get_dataset_context / datasets://{dataset_id}/context chamam GET /api/v1/datasets/{id}/context e devolvem, numa só leitura, o que antes exigia 6–8 chamadas. Sem LLM; limitado a 6000 caracteres (JSON) — quando não cabe, corta primeiro knowledge e depois schema e marca truncated: true com truncated_fields. Cada secção traz available; sem dados (ex.: dataset sem scan) vem available: false com reason em vez de erro.

Secção Conteúdo
identity nome, schema/tabela, connector_type, camada medallion, domínio {id, name}, owners só por nome
schema colunas: nome, tipo, nullable, descrição, sensibilidade e pii_type
quality último score e dimensões, tendência dos últimos 5 scores, issues abertos por severidade/tipo, verdict, regras falhadas (sem valores observados)
contract has_active, observed_only, versão ativa (number/status), conformidade (status + até 5 problemas)
lineage upstream/downstream a 1 salto, contagem de nós externos, has_column_lineage, exposures só nome/tipo/maturidade
readiness veredicto global de AI-readiness, por caso de uso e bloqueadores (sem amostras)
trust trust score, grade, componentes com evidência, bloqueadores
knowledge top-5 entradas da KB não obsoletas (corpo ≤ 300 chars) e excerto do README publicado (≤ 600 chars)
usage classe de uso e queries_30d
freshness last_modified_at do último snapshot de metadados e last_profiled_at

Nenhum valor de amostra, email ou URL sai (texto livre é saneado — [email], [url]).

Os resources reutilizam o DataVaultClient e os summarize_* partilhados com o chat — tools e resources devolvem exatamente os mesmos dados.

Readiness de data products

get_domain_readiness / domains://{domain_id}/readiness?use_case= fazem proxy de GET /api/v1/domains/{id}/readiness e devolvem o JSON da API tal e qual (a tool do chat devolve o mesmo, lido direto da sessão). Um agente ou pipeline que pergunta «o data product Vendas está pronto para RAG?» faz uma chamada em vez de N. Estados, códigos e o gate de CI: CI_QUALITY_GATE.md.

A readiness usa, por dataset, a mesma leitura da Trust API (#150) — as duas respostas nunca se contradizem — mas tem vocabulário próprio (agrega por produto):

Trust API (get_trust_decision, por dataset) Readiness (get_domain_readiness, por domínio)
no_scan (decisão unknown) no_scan — bloqueador
verdict_fail verdict_fail — bloqueador (e contract_invalid se o contrato ativo falhou)
verdict_warn verdict_warn — aviso
readiness_blocker ai_readiness_blocked — bloqueador
readiness_low — (o score vem em datasets[].ai_readiness.score)
scan_stale stale_scan — aviso (mesma regra: 2× a cadência)
critical_incident (incidentes, com lineage) open_critical_issues — issues críticos do próprio dataset
freshness_sla_breached slo_breach_open / attainment_below_objective — avisos, só com SLA no domínio
pii_unmasked_gold, warning_incident — (usa a Trust API por dataset)
— (a Trust API é por dataset) deprecated — bloqueador; draft — aviso: ciclo de vida do data product (cartão #168)

Exemplo LangChain — verificar a readiness do produto antes de indexar (ao lado do gate por documento de RAG_GATE.md):

from beryx import BeryX

readiness = BeryX().domain_readiness(VENDAS_DOMAIN_ID, use_case="rag")
if not readiness.ready:  # not_ready: não indexar
    raise RuntimeError(readiness.summary())
vectorstore.add_documents(splitter.split_documents(loader.load()))

Instalação

cd backend
pip install -e ".[mcp]"

Configuração (Claude Desktop / Claude Code)

{
  "mcpServers": {
    "datavault": {
      "command": "/caminho/backend/.venv/bin/python",
      "args": ["-m", "app.mcp_server"],
      "cwd": "/caminho/backend",
      "env": {
        "DATAVAULT_API_URL": "http://localhost:8000",
        "DATAVAULT_EMAIL": "utilizador@empresa.pt",
        "DATAVAULT_PASSWORD": "…"
      }
    }
  }
}

Em Claude Code: claude mcp add datavault -- /caminho/backend/.venv/bin/python -m app.mcp_server (com as mesmas variáveis de ambiente).

Modo Streamable HTTP (remoto)

Servidor de produção — https://mcp.praxis.lbostuff.tech/mcp

O MCP remoto está publicado (cartão #311): serviço Cloud Run datavault-mcp, TLS gerido, sem nada a instalar. Basta um token de API dvk_ (Equipa → Tokens de API, só admins) — a partir de qualquer máquina, sem este repositório:

claude mcp add --transport http datavault https://mcp.praxis.lbostuff.tech/mcp \
  --header "Authorization: Bearer dvk_…"

Noutro cliente MCP com suporte HTTP (Cursor, VS Code, Codex, Claude Desktop via mcp-remote), a página Ligar o teu agente da app gera a configuração exata — ver AGENTES_CODIGO.md. Formato genérico:

{
  "mcpServers": {
    "datavault": {
      "type": "http",
      "url": "https://mcp.praxis.lbostuff.tech/mcp",
      "headers": { "Authorization": "Bearer dvk_…" }
    }
  }
}

Testar com o MCP Inspector: npx @modelcontextprotocol/inspector → transporte Streamable HTTP, URL https://mcp.praxis.lbostuff.tech/mcp, header Authorization: Bearer dvk_….

Autenticação: cada pedido tem de trazer Authorization: Bearer dvk_…. Sem token, ou com um JWT de sessão, a resposta é 401. O token é validado contra a própria API (revogação e workspace respeitados) e é reencaminhado em todas as chamadas: cada cliente vê só o seu workspace. Se a API não responder, 503. OAuth 2.1 chega numa fatia seguinte (MCPV2).

Superfície só de leitura: em HTTP o servidor é stateless e não expõe diagnose_issue — e os tokens dvk_* são read-only na API (qualquer método que não GET dá 403), por isso nenhuma tool ou resource escreve.

Host e origem (anti-DNS-rebinding): a proteção do SDK só se liga sozinha com bind em localhost. O servidor valida ele próprio o header Host, antes da autenticação, contra uma allowlist: o host de DATAVAULT_MCP_PUBLIC_URL mais os de DATAVAULT_MCP_ALLOWED_HOSTS (separados por vírgula; em produção, o URL run.app determinístico). Fora da lista: 421. Um pedido com Origin (browser) só passa se a origem for https:// de um host da lista (http:// só em loopback); senão 403. Com bind em 0.0.0.0 e a allowlist vazia, o servidor recusa arrancar. Com bind em loopback, 127.0.0.1/localhost/::1 são aceites como no SDK.

Limites conhecidos:

  • Revogação até 60 s: as validações positivas do token ficam em cache por instância durante 60 s (por hash — o token nunca é guardado nem logado). Um token revogado pode continuar a servir até lá.
  • Rate limit por IP partilhado: a API aplica um limite por IP (1200 pedidos/min por instância, DATAVAULT_API_RATE_LIMIT_IP_REQUESTS) e outro por token (300/min). Todos os clientes MCP chegam à API a partir dos IPs de saída do datavault-mcp, por isso partilham o limite por IP; o limite por token continua a ser de cada cliente. O MCP não reencaminha X-Forwarded-For (seria falsificável). Cada tool faz 1–3 pedidos à API.
  • Arranque a frio: min-instances 0 — o primeiro pedido depois de um período sem tráfego demora alguns segundos.

Operação (deploy)

deploy/gcp_deploy.sh (secção 5e) cria o serviço a partir da imagem do backend (extra mcp no CONNECTOR_EXTRAS) com python -m app.mcp_server --transport http --host 0.0.0.0 --port 8080:

SA datavault-mcp — só logging.logWriter e monitoring.metricWriter; sem Cloud SQL, sem segredos, sem VPC
Env DATAVAULT_API_URL (https://api.${DOMAIN} — com o ingress fechado ao LB o run.app da api não responde a um Cloud Run sem VPC), DATAVAULT_MCP_PUBLIC_URL, DATAVAULT_MCP_ALLOWED_HOSTS
Escala 1 vCPU · 512 MiB · concurrency 20 · min 0 · max MCP_MAX_INSTANCES (3)
Probe arranque TCP 8080 (MCP_STARTUP_PROBE) — todo o /mcp exige Host e token
Domínio Em produção, mcp.${DOMAIN} é um host do Application Load Balancer do edge (deploy/lb_apply.sh, DEPLOY_GCP.md §4); sem LB, map_domain datavault-mcp "mcp.${DOMAIN}" e CNAME mcp → ghs.googlehosted.com. (com DNS_ZONE/DNS_PROJECT o script cria-o)

A api recebe DATAVAULT_MCP_PUBLIC_URL=https://mcp.${DOMAIN}/mcp, e é isso que faz GET /ide/agent-config e a página «Ligar o teu agente» gerarem a configuração real. O CD atualiza a imagem do datavault-mcp com a do backend. O deploy/validate_prod.sh (passo 16c, deploy/check_mcp_remote.sh) prova de fora, a cada deploy: o URL anunciado responde, sem token → 401, initialize e tools/list com um token dvk_ descartável (criado, usado e revogado) têm get_verdict e get_trust_decision, e um Host fora da allowlist → 421 (pelo URL legado *.a.run.app do serviço, que chega ao contentor mas não está na lista).

Servidor próprio (self-hosted / desenvolvimento)

Para correr o modo HTTP localmente (endpoint /mcp):

DATAVAULT_API_URL=http://localhost:8000 \
python -m app.mcp_server --transport http --host 127.0.0.1 --port 8765

Também configurável por ambiente: DATAVAULT_MCP_TRANSPORT=http, DATAVAULT_MCP_HOST, DATAVAULT_MCP_PORT. Exposto noutra máquina (bind 0.0.0.0, atrás de TLS), define DATAVAULT_MCP_PUBLIC_URL com o URL público — é a allowlist do Host. Na app, a mesma setting na api faz «Ligar o teu agente» apontar para ele; vazia, as configs apontam para http://127.0.0.1:8765/mcp e a página mostra o comando acima.

Recomendação (stdio, com DATAVAULT_EMAIL/DATAVAULT_PASSWORD): cria um utilizador member dedicado ao MCP (POST /auth/users, só admins) — os members não podem apagar conexões nem convidar utilizadores.

Exemplos de perguntas ao agente

  • «Qual é o dataset com pior score e quanto nos custa por mês?»
  • «O dataset clientes pode ser publicado?» (usa get_verdict)
  • «Posso usar gold.clientes num agente? Quem é o dono e o que já correu mal?» (usa get_dataset_context)
  • «O meu agente pode ler gold.clientes agora? E para RAG?» (usa get_trust_decision — a resposta diz também por quanto tempo vale)
  • «O que se sabe sobre a fiabilidade do gold.encomendas? Quem é o owner e há PII sem máscara?» (usa get_trust_facts)
  • «Quantos clientes do CRM não existem no ERP?» (usa match_datasets)
  • «Isto já aconteceu antes? Como resolvemos da última vez?» (usa search_knowledge)
  • «Há issues critical abertos? Diagnostica o mais recente.»
  • «Que sugestões de deduplicação temos com SQL pronto?»