Skip to main content

ONStec

MCP TIAGO Dados Abertos — ONS

tests PyPI Python License: Apache 2.0 MCP DuckDB ODCS

Servidor MCP que dá a um modelo de linguagem acesso aos dados abertos do ONS, o Operador Nacional do Sistema Elétrico.

Uma camada semântica em YAML (padrão ODCS) declara schema, unidades e agregações válidas de cada dataset. O servidor roteia, valida e executa; o modelo do cliente escreve o SQL. Cada resposta traz unidade e fonte provadas pelo contrato; quando não consegue provar, declara desconhecido em vez de deduzir.

A camada semântica reduz erros do modelo: entrega schema exato, tipos corretos e padrões SQL validados. Nenhum modelo de linguagem roda no servidor: ele entrega o catálogo e os contratos, valida e executa o SQL, e tudo o que devolve é determinístico e rastreável ao contrato.

O que dá errado sem a camada semântica

Para quem analisa o setor elétrico com um assistente de IA e precisa que o número esteja certo, não apenas plausível.

Pedir a carga do Sudeste em agosto a um modelo com acesso aos dados abertos falha em lugares que não são o SQL:

  • Escolher o dataset errado. O portal publica recortes parecidos com granularidades diferentes: horário e diário, por subsistema e por usina. A consulta roda, devolve um número plausível, e responde outra pergunta. O catálogo declara granularidade, perspectiva e uso de cada dataset, para a escolha ser pelo grão e não pelo nome.
  • Escrever SQL contra um schema imaginado. Coluna que não existe, tipo que não bate, data em texto comparada como texto. O contrato entrega o schema exato, os casts obrigatórios e padrões de SQL já validados antes de o modelo escrever a consulta.
  • Errar a unidade. MWmed não é MWh, e a diferença não aparece no resultado. Cada coluna devolvida vem com a unidade que o contrato prova.
  • Agregar o que não se agrega. Somar coluna que já é média, ou totalizar linhas que já são um total, dobra o valor sem erro nenhum. O contrato declara a aditividade de cada coluna, e ela acompanha a resposta.
  • Perder a procedência. Número em relatório sem saber de onde veio. Cada resposta cita o dataset lido e a URL dele no portal.

A garantia não é que o modelo acerte o SQL: é que o servidor só afirma o que o contrato sustenta. Quando não sustenta, marca o campo como desconhecido em vez de deduzir.

O que o servidor devolve

O modelo escreve a pergunta; o servidor devolve dado, unidade e fonte, provados pelo contrato.

Pergunta: Qual foi a carga do Sudeste em agosto de 2020?

SELECT id_subsistema, ROUND(AVG(TRY_CAST(val_cargaenergiamwmed AS DOUBLE)), 1) AS carga_mwmed
FROM read_parquet('s3://ons-aws-prod-opendata/dataset/carga_energia_di/*.parquet')
WHERE id_subsistema = 'SE' AND din_instante >= '2020-08-01' AND din_instante < '2020-09-01'
GROUP BY id_subsistema
id_subsistema carga_mwmed
SE 34682.8
{"columns": [{"name": "carga_mwmed", "agg": "AVG", "sources": ["val_cargaenergiamwmed"],
              "quantity_kind": "average_power", "unit": "MWmed", "additive": false}]}
{"sources": [{"dataset": "carga-energia", "portal_url": "https://dados.ons.org.br/dataset/carga-energia"}]}

Os exemplos usam 2020 de propósito: a fonte do ONS passa por consistência recorrente e revisa períodos recentes, então um recorte antigo é o que se mantém estável. O que o servidor garante em qualquer período é a forma da resposta: a unidade, a aditividade e a fonte, que vêm do contrato.

A unidade MWmed e a fonte carga-energia não vêm do modelo, vêm do contrato. Se o contrato não declarasse, os blocos não apareceriam. Os blocos acima mostram os campos principais; o result-schema real traz ainda role, mode e unit_tier, e uma entrada para cada coluna do resultado, inclusive as de dimensão.

Pergunta: Qual era a energia armazenada por subsistema em agosto de 2020?

SELECT nom_subsistema, ROUND(AVG(TRY_CAST(ear_verif_subsistema_mwmes AS DOUBLE)), 0) AS ear_mwmes
FROM read_parquet('s3://ons-aws-prod-opendata/dataset/ear_subsistema_di/*.parquet')
WHERE ear_data >= '2020-08-01' AND ear_data < '2020-09-01'
GROUP BY nom_subsistema
nom_subsistema ear_mwmes
SUDESTE 91461
NORDESTE 40821
SUL 11687
NORTE 11346
{"columns": [{"name": "ear_mwmes", "agg": "AVG", "sources": ["ear_verif_subsistema_mwmes"],
              "quantity_kind": "energy_stock", "unit": "MWmes", "additive": false}]}
{"sources": [{"dataset": "ear-diario-por-subsistema", "portal_url": "https://dados.ons.org.br/dataset/ear-diario-por-subsistema"}]}

O additive: false avisa que estes valores não se somam ao longo do tempo: energia armazenada é estoque, uma foto do reservatório naquele dia. Somar o que havia em agosto com o que havia em setembro contaria a mesma água duas vezes. Somar os quatro subsistemas no mesmo dia é outra coisa, e é válido: dá o armazenamento do SIN.

Por que este servidor e não consulta direta?

Aspecto Sem o servidor Com o servidor
Preparação Baixar CSV, tratar em pandas/Excel, converter tipos Pergunta direto; o servidor lê o S3 público
Escolha do dataset O modelo adivinha pelo nome Catálogo com granularidade, perspectiva e uso
Schema O modelo inventa colunas Contrato entrega o schema exato
Tipos Cast errado falha em silêncio TRY_CAST obrigatório, declarado no contrato
Unidade Não se sabe se é MWh ou MWmed Unidade provada pelo contrato
Dupla contagem Somar SIN e subsistemas dobra o valor Contrato avisa sobre a linha agregada
Procedência Sem rastro Cada resposta cita o dataset e a URL
Segurança SQL arbitrário Validador falha fechado, só leitura

Nota: O servidor entrega o contexto certo (schema, métricas, fonte). A qualidade do SQL e da interpretação depende do modelo do cliente. LLMs ainda erram; o servidor reduz a superfície de erro, não a elimina.

Como funciona

O contrato de cada dataset declara também granularidade, casts obrigatórios e as perguntas que aquele dataset responde. É a única fonte de verdade: não existe configuração paralela, e tudo o que o servidor afirma sai de um campo do contrato.

flowchart LR
    C["Cliente MCP<br/>(o modelo raciocina aqui)"] -->|lê o catálogo| L["listar_datasets<br/>catálogo inteiro<br/>(também resource)"]
    C -.->|filtro por termo| B["buscar_dataset<br/>ranking dos contratos"]
    L --> D["descrever_dataset<br/>schema, casts, padrões SQL"]
    B -.-> D
    D -->|o modelo escreve o SQL| E["executar_sql<br/>validador falha fechado"]
    E --> K["DuckDB<br/>zero ETL"]
    K <-->|Parquet| S3[("S3 público<br/>do ONS")]
    Ct[("contracts/<br/>ODCS")] -.-> L & B & D & E
  • Roteamento pelo catálogo. listar_datasets entrega o catálogo inteiro, uma linha por dataset com granularidade, perspectiva e uso, e o modelo escolhe pelo assunto e pelo grão. buscar_dataset filtra por termo, com palavra-chave e BM25, para catálogo grande. descrever_dataset mostra o schema, as unidades e os avisos que o modelo precisa antes de escrever SQL.
  • Execução segura. executar_sql aceita só leitura, só das origens do ONS, e devolve o resultado com a unidade e a aditividade de cada coluna provadas pelo contrato.
  • Só afirma o que prova. Quando o contrato não sustenta uma unidade ou uma agregação, o servidor devolve unit: null e unit_tier: "unknown" em vez de deduzir um valor plausível. Fonte sem URL canônica no contrato é omitida do bloco de proveniência. Abstenção declarada, não erro.
  • Sem cópia dos dados. O DuckDB lê o bucket público do ONS no momento da consulta, de forma anônima.

Cada resposta de executar_sql é a tabela em markdown seguida de dois blocos JSON: result-schema, com a unidade e a aditividade de cada coluna e o tier do contrato que as provou; e tiago-dados-abertos-sources, com o dataset lido e a URL no portal. Bloco que o contrato não sustenta não aparece.

Usar sem instalar

O servidor está disponível publicamente em:

https://mcp.dados.tiago.ons.org.br/

Veja instruções para cada cliente em dados.ons.org.br/mcp.

Instalação

Requisitos: Python 3.12 ou mais recente e acesso à internet. O bucket do ONS é público e lido anonimamente.

O pacote está no PyPI. Para usar sem clonar o repositório:

uvx mcp-tiago-dados-abertos --stdio     # sem instalar nada
pip install mcp-tiago-dados-abertos     # ou instalando no ambiente

Para trabalhar no código, clone:

git clone https://github.com/ONSBR/TIAGO-Dados-Abertos.git
cd TIAGO-Dados-Abertos
python -m venv .venv

# Linux/macOS
source .venv/bin/activate
# Windows
.\.venv\Scripts\activate

pip install -e ".[dev]"
python scripts/build_contracts_cache.py   # acelera o boot; opcional

Rodar

# STDIO (para IDEs e Claude Desktop)
mcp-tiago-dados-abertos --stdio

# HTTP
mcp-tiago-dados-abertos
# MCP:      http://localhost:8003/mcp
# health:   http://localhost:8003/health
# catálogo: http://localhost:8003/datasets

Docker

docker build -t mcp-tiago-dados-abertos .
docker run -p 8000:8000 mcp-tiago-dados-abertos

Os contratos vão embutidos na imagem e o cache é gerado no build. A porta é 8003 no run local e 8000 no container: o endpoint MCP do container fica em http://localhost:8000/mcp e o health em http://localhost:8000/health. A imagem traz um HEALTHCHECK que consulta esse endpoint a cada 30 segundos.

Configurar no cliente MCP

Troque <ABS_PATH> pelo caminho absoluto do repositório. Em Linux/macOS o comando é <ABS_PATH>/.venv/bin/mcp-tiago-dados-abertos. O arquivo examples/mcp.json tem o bloco pronto para copiar.

cliente onde forma
Claude Desktop claude_desktop_config.json mcpServers com command e args
Claude Code terminal claude mcp add tiago-dados-abertos -- <ABS_PATH>/.venv/Scripts/mcp-tiago-dados-abertos.exe --stdio
Cursor .cursor/mcp.json mcpServers com command e args
Kiro .kiro/settings/mcp.json mcpServers com command e args
Windsurf ~/.codeium/windsurf/mcp_config.json mcpServers com command e args
Gemini CLI ~/.gemini/settings.json mcpServers com command e args
VS Code .vscode/mcp.json servers com "type": "stdio", command e args

Para os clientes com mcpServers:

{
  "mcpServers": {
    "tiago-dados-abertos": {
      "command": "<ABS_PATH>/.venv/Scripts/mcp-tiago-dados-abertos.exe",
      "args": ["--stdio"]
    }
  }
}

Para o VS Code:

{
  "servers": {
    "tiago-dados-abertos": {
      "type": "stdio",
      "command": "<ABS_PATH>/.venv/Scripts/mcp-tiago-dados-abertos.exe",
      "args": ["--stdio"]
    }
  }
}

Com o servidor em modo HTTP ou no container, Cursor, Kiro, VS Code e Claude Code aceitam a URL no lugar de command e args: "url": "http://localhost:8000/mcp" (porta 8003 fora do container), no VS Code com "type": "http", e no Claude Code claude mcp add --transport http tiago-dados-abertos http://localhost:8000/mcp.

Tools

tool o que faz
listar_datasets o catálogo inteiro, um dataset por linha, com granularidade, perspectiva temporal e uso; a entrada padrão do fluxo
buscar_dataset filtra o catálogo por termo, com palavra-chave e BM25; para catálogo grande ou termo específico
descrever_dataset schema, métricas, unidades, avisos de tipo, patterns SQL e exemplos
executar_sql executa SQL DuckDB somente leitura, paginado, com unidade, fatos e fonte provados

O conteúdo de listar_datasets também é o resource catalogo://datasets, para clientes que fixam resources no contexto.

Fluxo recomendado:

1. listar_datasets()                    → lê o catálogo e escolhe pelo assunto e pelo grão
2. descrever_dataset("carga-energia")   → lê schema, unidades e avisos
3. executar_sql("SELECT ...")           → executa com as colunas reais

Nunca gere SQL sem ler o schema antes. As colunas têm os nomes do ONS, não os que parecem óbvios, e várias métricas exigem o cast declarado no contrato.

Configuração

Tudo é variável de ambiente. Defina no ambiente do processo, no bloco env do cliente MCP, ou num arquivo .env na raiz do repositório, que o servidor carrega no boot. Variável já definida no ambiente vence a do arquivo. Nenhuma é obrigatória. Há um .env.example com todas comentadas.

Servidor

variável padrão efeito
MCP_HOST 0.0.0.0 endereço de bind no modo HTTP
MCP_PORT 8003 porta no modo HTTP (o Dockerfile fixa 8000)
MCP_STATELESS true sessões HTTP sem estado; deixe true atrás de balanceador
MCP_ENV_FILE .env na raiz do repositório caminho do arquivo .env; num pacote instalado, aponte explicitamente
MCP_CONTRACTS_DIR os contratos embutidos no pacote só para apontar outro diretório de contratos; um contracts_cache.json válido na raiz dele substitui a leitura dos YAML

Execução de SQL

variável padrão efeito
MCP_QUERY_TIMEOUT_S 30 segundos até o servidor interromper a consulta e sugerir reduzir o escopo
MCP_DUCKDB_MEMORY_LIMIT 2GB teto de memória do DuckDB
MCP_MAX_PAGE_LIMIT 1000 maior limit aceito em executar_sql; pedidos acima são reduzidos
MCP_MAX_SQL_CHARS 65536 maior SQL aceito em executar_sql, em caracteres; acima disso a consulta é recusada antes de qualquer análise
MCP_HTTP_UA mcp-tiago-dados-abertos/1.0 (contato@exemplo.com) User-Agent das leituras HTTPS. Troque o contato antes de expor o servidor: é como o ONS identifica quem consulta

A conexão com o DuckDB é anônima de propósito e não recebe credencial AWS. O bucket do ONS é público, e a ausência de chave é o que impede um SQL injetado de ler um bucket privado da sua conta. Não acrescente credencial. A região é fixa em us-west-2, onde o bucket do ONS vive.

Limites de abuso

variável padrão efeito
MCP_RATE_LIMIT_GLOBAL_MAX 600 consultas por minuto no processo inteiro, somando todos os clientes
MCP_RATE_LIMIT_MAX_KEYS 20000 teto de chaves distintas no limitador, contra crescimento de memória

O limite é do processo, mais uma rajada fixa de 20 consultas por segundo. Não há limite por cliente: o servidor não distingue quem chama. Se você expuser o modo HTTP para além da sua rede, ponha um limite por cliente no proxy ou gateway à frente.

Datas e diagnóstico

variável padrão efeito
MCP_DATA_TZ America/Sao_Paulo fuso em que "hoje", "ontem" e "mês passado" são resolvidos. É o fuso dos dados, não do servidor, e aparece no rodapé de contexto temporal de toda resposta
MCP_CORR_HEADER x-request-id cabeçalho HTTP de onde vem o id de correlação da requisição; atrás de um proxy que gera o próprio id, aponte para o cabeçalho dele
MCP_COBERTURA_PROBE vazio 1 faz descrever_dataset consultar o S3 para descobrir a data mais recente de uma série ativa, com cache de uma hora por dataset e desistência em 8 segundos. Desligado por padrão

Estrutura do repositório

TIAGO-Dados-Abertos/
├── src/mcp_tiago_dados_abertos/
│   ├── tools/           # 4 tools MCP (buscar, listar, descrever, executar_sql)
│   ├── catalogo/        # carrega contratos, BM25, ranking
│   ├── execucao/        # validador SQL, poda de glob por ano, DuckDB, rate limiter
│   ├── resposta/        # result-schema, aditividade, proveniência, aviso de unidade, rodapé temporal
│   ├── infra/           # config, telemetria, correlação
│   └── server.py        # entrypoint MCP (tools + resource catalogo://datasets)
├── contracts/ons/       # contratos ODCS (camada semântica), um por dataset
├── tests/               # catalogo, execucao, tools e corpus (integridade dos contratos)
├── scripts/             # build_contracts_cache.py
├── docs/                # ROUTING.md e assets do README
└── examples/            # mcp.json de exemplo

Contribuir e reportar

Como contribuir com contratos ou com o código: CONTRIBUTING.md. Camada semântica e estrutura dos contratos: CONTRACTS.md. Algoritmo de roteamento e calibração: docs/ROUTING.md. Vulnerabilidade: SECURITY.md. Mudanças entre versões: CHANGELOG.md.

Licença

O código é Apache 2.0: LICENSE. Os dados são do ONS, sob CC-BY, e seguem os termos do Portal de Dados Abertos do ONS; ver NOTICE. Este projeto não redistribui dados: ele lê a origem oficial no momento da consulta.

Metadata

Release files for mcp-tiago-dados-abertos 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-tiago-dados-abertos 1.1.0
File Size Uploaded Provenance
mcp_tiago_dados_abertos-1.1.0.tar.gz 696.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-tiago-dados-abertos 1.1.0
File Interpreter ABI Platform
mcp_tiago_dados_abertos-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.3 MB

Release files / mcp_tiago_dados_abertos-1.1.0.tar.gz

Download URL mcp_tiago_dados_abertos-1.1.0.tar.gz
Size 696.1 kB
Tags Source
SHA-256 checksum
How to use checksums
7d0774f6ca85135338589a8bfc0d0ab4d8a024216f9819b597d4b663afe60f56
BLAKE2b-256 checksum
How to use checksums
d51d72986bca45fc60966f0777d5b69e4c52f793dcba17f8968f34c6057e70f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / mcp_tiago_dados_abertos-1.1.0-py3-none-any.whl

Download URL mcp_tiago_dados_abertos-1.1.0-py3-none-any.whl
Size 558.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8afcdccf886c2cfba3b8515eca3ce616a30a9b407d456b82791c2e87cac458aa
BLAKE2b-256 checksum
How to use checksums
c351e6f25e1dc8844a036bc8bed9f2c0fad55cc1569f74abd810091fac71eb4f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Security for mcp-tiago-dados-abertos 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mcp_tiago_dados_abertos-1.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page